mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: resolved merge conflicts
This commit is contained in:
commit
28c6025349
@ -14,6 +14,7 @@
|
|||||||
"@headlessui/react": "^1.7.19",
|
"@headlessui/react": "^1.7.19",
|
||||||
"@plane/types": "*",
|
"@plane/types": "*",
|
||||||
"@plane/ui": "*",
|
"@plane/ui": "*",
|
||||||
|
"@plane/constants": "*",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@types/lodash": "^4.17.0",
|
"@types/lodash": "^4.17.0",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
"packages/tailwind-config-custom",
|
"packages/tailwind-config-custom",
|
||||||
"packages/tsconfig",
|
"packages/tsconfig",
|
||||||
"packages/ui",
|
"packages/ui",
|
||||||
"packages/types"
|
"packages/types",
|
||||||
|
"packages/constants"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
@ -25,7 +26,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^10.4.15",
|
"autoprefixer": "^10.4.15",
|
||||||
"eslint-config-custom": "*",
|
"eslint-config-custom": "*",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.29",
|
||||||
"prettier": "latest",
|
"prettier": "latest",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.4",
|
"prettier-plugin-tailwindcss": "^0.5.4",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
|
10
packages/constants/package.json
Normal file
10
packages/constants/package.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "@plane/constants",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"main": "./src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts",
|
||||||
|
"./*": "./src/*"
|
||||||
|
}
|
||||||
|
}
|
360
packages/constants/src/auth.ts
Normal file
360
packages/constants/src/auth.ts
Normal file
@ -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) => (
|
||||||
|
<div>
|
||||||
|
Your account is already registered.
|
||||||
|
<Link
|
||||||
|
className="underline underline-offset-4 font-medium hover:font-bold transition-all"
|
||||||
|
href={`/sign-in${email ? `?email=${email}` : ``}`}
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
now.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
[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) => (
|
||||||
|
<div>
|
||||||
|
No account found.
|
||||||
|
<Link
|
||||||
|
className="underline underline-offset-4 font-medium hover:font-bold transition-all"
|
||||||
|
href={`/${email ? `?email=${email}` : ``}`}
|
||||||
|
>
|
||||||
|
Create one
|
||||||
|
</Link>
|
||||||
|
to get started.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
[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: () => (
|
||||||
|
<div>
|
||||||
|
Admin user already exists.
|
||||||
|
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
now.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: {
|
||||||
|
title: `Admin user does not exist`,
|
||||||
|
message: () => (
|
||||||
|
<div>
|
||||||
|
Admin user does not exist.
|
||||||
|
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
now.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
1
packages/constants/src/index.ts
Normal file
1
packages/constants/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./auth";
|
@ -13,7 +13,7 @@ module.exports = {
|
|||||||
plugins: ["react", "@typescript-eslint", "import"],
|
plugins: ["react", "@typescript-eslint", "import"],
|
||||||
settings: {
|
settings: {
|
||||||
next: {
|
next: {
|
||||||
rootDir: ["web/", "space/", "packages/*/"],
|
rootDir: ["web/", "space/", "admin/", "packages/*/"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
5
space/app/[workspace_slug]/[project_id]/error.tsx
Normal file
5
space/app/[workspace_slug]/[project_id]/error.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function ProjectError() {
|
||||||
|
return <>Project Error</>;
|
||||||
|
}
|
38
space/app/[workspace_slug]/[project_id]/layout.tsx
Normal file
38
space/app/[workspace_slug]/[project_id]/layout.tsx
Normal file
@ -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 (
|
||||||
|
<div className="relative flex h-screen min-h-[500px] w-screen flex-col overflow-hidden">
|
||||||
|
<div className="relative flex h-[60px] flex-shrink-0 select-none items-center border-b border-custom-border-300 bg-custom-sidebar-background-100">
|
||||||
|
<IssueNavbar projectSettings={projectSettings} />
|
||||||
|
</div>
|
||||||
|
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
|
||||||
|
<a
|
||||||
|
href="https://plane.so"
|
||||||
|
className="fixed bottom-2.5 right-5 !z-[999999] flex items-center gap-1 rounded border border-custom-border-200 bg-custom-background-100 px-2 py-1 shadow-custom-shadow-2xs"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>
|
||||||
|
<div className="relative grid h-6 w-6 place-items-center">
|
||||||
|
<Image src={planeLogo} alt="Plane logo" className="h-6 w-6" height="24" width="24" />
|
||||||
|
</div>
|
||||||
|
<div className="text-xs">
|
||||||
|
Powered by <span className="font-semibold">Plane Deploy</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
8
space/app/[workspace_slug]/[project_id]/page.tsx
Normal file
8
space/app/[workspace_slug]/[project_id]/page.tsx
Normal file
@ -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 <ProjectDetailsView workspaceSlug={workspace_slug} projectId={project_id} params={rest} />;
|
||||||
|
}
|
5
space/app/error.tsx
Normal file
5
space/app/error.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function InstanceError() {
|
||||||
|
return <div>Instance Error</div>;
|
||||||
|
}
|
49
space/app/layout.tsx
Normal file
49
space/app/layout.tsx
Normal file
@ -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 (
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
{/* <link rel="apple-touch-icon" sizes="180x180" href={`${prefix}favicon/apple-touch-icon.png`} />
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href={`${prefix}favicon/favicon-32x32.png`} />
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href={`${prefix}favicon/favicon-16x16.png`} />
|
||||||
|
<link rel="manifest" href={`${prefix}site.webmanifest.json`} />
|
||||||
|
<link rel="shortcut icon" href={`${prefix}favicon/favicon.ico`} /> */}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{!instanceDetails?.instance?.is_setup_done ? (
|
||||||
|
<InstanceNotReady />
|
||||||
|
) : (
|
||||||
|
<AppProvider initialState={{ instance: instanceDetails.instance }}>{children}</AppProvider>
|
||||||
|
)}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
31
space/app/page.tsx
Normal file
31
space/app/page.tsx
Normal file
@ -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 <UserLoggedIn />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return <>Login View</>;
|
||||||
|
return <AuthView />;
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
// icons
|
// icons
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
"use client";
|
||||||
import { Fragment, useState } from "react";
|
import { Fragment, useState } from "react";
|
||||||
import { usePopper } from "react-popper";
|
import { usePopper } from "react-popper";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
// icons
|
// icons
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/navigation";
|
||||||
import { Eye, EyeOff, XCircle } from "lucide-react";
|
import { Eye, EyeOff, XCircle } from "lucide-react";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Input, Spinner } from "@plane/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";
|
import { getPasswordStrength } from "@/helpers/password.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
import { AuthService } from "@/services/authentication.service";
|
import { AuthService } from "@/services/auth.service";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
email: string;
|
email: string;
|
||||||
@ -43,12 +45,12 @@ export const PasswordForm: React.FC<Props> = (props) => {
|
|||||||
const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
|
const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
// hooks
|
// hooks
|
||||||
const { instance } = useInstance();
|
const { data: instance, config: instanceConfig } = useInstance();
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { next_path } = router.query;
|
const { next_path } = router.query;
|
||||||
// derived values
|
// derived values
|
||||||
const isSmtpConfigured = instance?.config?.is_smtp_configured;
|
const isSmtpConfigured = instanceConfig?.is_smtp_configured;
|
||||||
|
|
||||||
const handleFormChange = (key: keyof TPasswordFormValues, value: string) =>
|
const handleFormChange = (key: keyof TPasswordFormValues, value: string) =>
|
||||||
setPasswordFormData((prev) => ({ ...prev, [key]: value }));
|
setPasswordFormData((prev) => ({ ...prev, [key]: value }));
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// components
|
// components
|
||||||
@ -7,7 +9,7 @@ import { EmailForm, UniqueCodeForm, PasswordForm, OAuthOptions, TermsAndConditio
|
|||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
import useToast from "@/hooks/use-toast";
|
import useToast from "@/hooks/use-toast";
|
||||||
// services
|
// services
|
||||||
import { AuthService } from "@/services/authentication.service";
|
import { AuthService } from "@/services/auth.service";
|
||||||
|
|
||||||
export enum EAuthSteps {
|
export enum EAuthSteps {
|
||||||
EMAIL = "EMAIL",
|
EMAIL = "EMAIL",
|
||||||
@ -60,9 +62,9 @@ export const AuthRoot = observer(() => {
|
|||||||
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
|
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
// hooks
|
// hooks
|
||||||
const { instance } = useInstance();
|
const { config: instanceConfig } = useInstance();
|
||||||
// derived values
|
// derived values
|
||||||
const isSmtpConfigured = instance?.config?.is_smtp_configured;
|
const isSmtpConfigured = instanceConfig?.is_smtp_configured;
|
||||||
|
|
||||||
const { header, subHeader } = getHeaderSubHeader(authMode);
|
const { header, subHeader } = getHeaderSubHeader(authMode);
|
||||||
|
|
||||||
@ -112,8 +114,8 @@ export const AuthRoot = observer(() => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isOAuthEnabled =
|
const isOAuthEnabled = instanceConfig && (instanceConfig?.is_google_enabled || instanceConfig?.is_github_enabled);
|
||||||
instance?.config && (instance?.config?.is_google_enabled || instance?.config?.is_github_enabled);
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-col space-y-6">
|
<div className="relative flex flex-col space-y-6">
|
||||||
<div className="space-y-1 text-center">
|
<div className="space-y-1 text-center">
|
||||||
@ -149,7 +151,7 @@ export const AuthRoot = observer(() => {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{isOAuthEnabled && <OAuthOptions />}
|
{isOAuthEnabled !== undefined && <OAuthOptions />}
|
||||||
<TermsAndConditions mode={authMode} />
|
<TermsAndConditions mode={authMode} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/navigation";
|
||||||
// icons
|
// icons
|
||||||
import { CircleCheck, XCircle } from "lucide-react";
|
import { CircleCheck, XCircle } from "lucide-react";
|
||||||
// ui
|
// ui
|
||||||
@ -10,7 +12,7 @@ import { API_BASE_URL } from "@/helpers/common.helper";
|
|||||||
import useTimer from "@/hooks/use-timer";
|
import useTimer from "@/hooks/use-timer";
|
||||||
import useToast from "@/hooks/use-toast";
|
import useToast from "@/hooks/use-toast";
|
||||||
// services
|
// services
|
||||||
import { AuthService } from "@/services/authentication.service";
|
import { AuthService } from "@/services/auth.service";
|
||||||
// types
|
// types
|
||||||
import { IEmailCheckData } from "@/types/auth";
|
import { IEmailCheckData } from "@/types/auth";
|
||||||
import { EAuthModes } from "./root";
|
import { EAuthModes } from "./root";
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// components
|
// components
|
||||||
import { GithubOAuthButton, GoogleOAuthButton } from "@/components/accounts";
|
import { GithubOAuthButton, GoogleOAuthButton } from "@/components/accounts";
|
||||||
@ -6,7 +8,7 @@ import { useInstance } from "@/hooks/store";
|
|||||||
|
|
||||||
export const OAuthOptions: React.FC = observer(() => {
|
export const OAuthOptions: React.FC = observer(() => {
|
||||||
// hooks
|
// hooks
|
||||||
const { instance } = useInstance();
|
const { config: instanceConfig } = useInstance();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -16,12 +18,12 @@ export const OAuthOptions: React.FC = observer(() => {
|
|||||||
<hr className="w-full border-onboarding-border-100" />
|
<hr className="w-full border-onboarding-border-100" />
|
||||||
</div>
|
</div>
|
||||||
<div className={`mx-auto mt-7 grid gap-4 overflow-hidden sm:w-96`}>
|
<div className={`mx-auto mt-7 grid gap-4 overflow-hidden sm:w-96`}>
|
||||||
{instance?.config?.is_google_enabled && (
|
{instanceConfig?.is_google_enabled && (
|
||||||
<div className="flex h-[42px] items-center !overflow-hidden">
|
<div className="flex h-[42px] items-center !overflow-hidden">
|
||||||
<GoogleOAuthButton text="SignIn with Google" />
|
<GoogleOAuthButton text="SignIn with Google" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{instance?.config?.is_github_enabled && <GithubOAuthButton text="SignIn with Github" />}
|
{instanceConfig?.is_github_enabled && <GithubOAuthButton text="SignIn with Github" />}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
@ -8,7 +10,7 @@ import { Button, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
|||||||
// components
|
// components
|
||||||
import { UserImageUploadModal } from "@/components/accounts";
|
import { UserImageUploadModal } from "@/components/accounts";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMobxStore } from "@/hooks/store";
|
import { useUser } from "@/hooks/store";
|
||||||
// services
|
// services
|
||||||
import fileService from "@/services/file.service";
|
import fileService from "@/services/file.service";
|
||||||
|
|
||||||
@ -35,9 +37,7 @@ export const OnBoardingForm: React.FC<Props> = observer((props) => {
|
|||||||
const [isRemoving, setIsRemoving] = useState(false);
|
const [isRemoving, setIsRemoving] = useState(false);
|
||||||
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
|
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const { updateCurrentUser } = useUser();
|
||||||
user: { updateCurrentUser },
|
|
||||||
} = useMobxStore();
|
|
||||||
// form info
|
// form info
|
||||||
const {
|
const {
|
||||||
getValues,
|
getValues,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
import { CircleCheck } from "lucide-react";
|
import { CircleCheck } from "lucide-react";
|
||||||
// helpers
|
// helpers
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { EAuthModes } from "./auth-forms";
|
import { EAuthModes } from "./auth-forms";
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
"use client";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
@ -27,7 +28,7 @@ export const UserImageUploadModal: React.FC<Props> = observer((props) => {
|
|||||||
const [image, setImage] = useState<File | null>(null);
|
const [image, setImage] = useState<File | null>(null);
|
||||||
const [isImageUploading, setIsImageUploading] = useState(false);
|
const [isImageUploading, setIsImageUploading] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { instance } = useInstance();
|
const { config: instanceConfig } = useInstance();
|
||||||
|
|
||||||
const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]);
|
const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]);
|
||||||
|
|
||||||
@ -36,7 +37,7 @@ export const UserImageUploadModal: React.FC<Props> = observer((props) => {
|
|||||||
accept: {
|
accept: {
|
||||||
"image/*": [".png", ".jpg", ".jpeg", ".svg", ".webp"],
|
"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,
|
multiple: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useUser } from "@/hooks/store";
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
|
||||||
// ui
|
// ui
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
@ -9,10 +10,10 @@ import { ADMIN_BASE_URL, ADMIN_BASE_PATH } from "@/helpers/common.helper";
|
|||||||
import PlaneTakeOffImage from "@/public/instance/plane-takeoff.png";
|
import PlaneTakeOffImage from "@/public/instance/plane-takeoff.png";
|
||||||
|
|
||||||
export const InstanceNotReady: FC = () => {
|
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 (
|
return (
|
||||||
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center mt-10">
|
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center pt-12">
|
||||||
<div className="w-auto max-w-2xl relative space-y-8 py-10">
|
<div className="w-auto max-w-2xl relative space-y-8 py-10">
|
||||||
<div className="relative flex flex-col justify-center items-center space-y-4">
|
<div className="relative flex flex-col justify-center items-center space-y-4">
|
||||||
<h1 className="text-3xl font-bold pb-3">Welcome aboard Plane!</h1>
|
<h1 className="text-3xl font-bold pb-3">Welcome aboard Plane!</h1>
|
||||||
@ -21,13 +22,12 @@ export const InstanceNotReady: FC = () => {
|
|||||||
Get started by setting up your instance and workspace
|
Get started by setting up your instance and workspace
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Link href={GOD_MODE_URL}>
|
<a href={GOD_MODE_URL}>
|
||||||
<Button size="lg" className="w-full">
|
<Button size="lg" className="w-full">
|
||||||
Get started
|
Get started
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
// mobx react lite
|
// mobx react lite
|
||||||
import { observer } from "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 { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date";
|
||||||
import { IssueBlockPriority } from "@/components/issues/board-views/block-priority";
|
import { IssueBlockPriority } from "@/components/issues/board-views/block-priority";
|
||||||
import { IssueBlockState } from "@/components/issues/board-views/block-state";
|
import { IssueBlockState } from "@/components/issues/board-views/block-state";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date";
|
import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date";
|
||||||
import { IssueBlockLabels } from "@/components/issues/board-views/block-labels";
|
import { IssueBlockLabels } from "@/components/issues/board-views/block-labels";
|
||||||
|
@ -2,27 +2,24 @@ import { observer } from "mobx-react-lite";
|
|||||||
// components
|
// components
|
||||||
import { IssueListBlock } from "@/components/issues/board-views/list/block";
|
import { IssueListBlock } from "@/components/issues/board-views/list/block";
|
||||||
import { IssueListHeader } from "@/components/issues/board-views/list/header";
|
import { IssueListHeader } from "@/components/issues/board-views/list/header";
|
||||||
// interfaces
|
|
||||||
// mobx hook
|
// mobx hook
|
||||||
import { useMobxStore } from "@/hooks/store";
|
import { useIssue } from "@/hooks/store";
|
||||||
// store
|
// types
|
||||||
import { RootStore } from "@/store/root.store";
|
import { IIssueState, IIssue } from "@/types/issue";
|
||||||
import { IIssueState, IIssue } from "types/issue";
|
|
||||||
|
|
||||||
export const IssueListView = observer(() => {
|
export const IssueListView = observer(() => {
|
||||||
const { issue: issueStore }: RootStore = useMobxStore();
|
const { states, getFilteredIssuesByState } = useIssue();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{issueStore?.states &&
|
{states &&
|
||||||
issueStore?.states.length > 0 &&
|
states.length > 0 &&
|
||||||
issueStore?.states.map((_state: IIssueState) => (
|
states.map((_state: IIssueState) => (
|
||||||
<div key={_state.id} className="relative w-full">
|
<div key={_state.id} className="relative w-full">
|
||||||
<IssueListHeader state={_state} />
|
<IssueListHeader state={_state} />
|
||||||
{issueStore.getFilteredIssuesByState(_state.id) &&
|
{getFilteredIssuesByState(_state.id) && getFilteredIssuesByState(_state.id).length > 0 ? (
|
||||||
issueStore.getFilteredIssuesByState(_state.id).length > 0 ? (
|
|
||||||
<div className="divide-y divide-custom-border-200">
|
<div className="divide-y divide-custom-border-200">
|
||||||
{issueStore.getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
|
{getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
|
||||||
<IssueListBlock key={_issue.id} issue={_issue} />
|
<IssueListBlock key={_issue.id} issue={_issue} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,33 +1,31 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { FC, useCallback } from "react";
|
import { FC, useCallback } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/navigation";
|
||||||
// components
|
// hooks
|
||||||
|
import { useIssue, useProject, useIssueFilter } from "@/hooks/store";
|
||||||
// store
|
// store
|
||||||
import { useMobxStore } from "@/hooks/store";
|
import { IIssueFilterOptions } from "@/types/issue";
|
||||||
import { IIssueFilterOptions } from "@/store/issues/types";
|
// components
|
||||||
import { RootStore } from "@/store/root.store";
|
|
||||||
import { AppliedFiltersList } from "./filters-list";
|
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 router = useRouter();
|
||||||
const { workspace_slug: workspaceSlug, project_slug: projectId } = router.query as {
|
const { workspaceSlug, projectId } = props;
|
||||||
workspace_slug: string;
|
const { states, labels } = useIssue();
|
||||||
project_slug: string;
|
const { activeLayout } = useProject();
|
||||||
};
|
const { issueFilters, updateFilters } = useIssueFilter();
|
||||||
|
|
||||||
const {
|
|
||||||
issuesFilter: { issueFilters, updateFilters },
|
|
||||||
issue: { states, labels },
|
|
||||||
project: { activeBoard },
|
|
||||||
}: RootStore = useMobxStore();
|
|
||||||
|
|
||||||
const userFilters = issueFilters?.filters || {};
|
const userFilters = issueFilters?.filters || {};
|
||||||
|
|
||||||
const appliedFilters: IIssueFilterOptions = {};
|
const appliedFilters: any = {};
|
||||||
|
|
||||||
Object.entries(userFilters).forEach(([key, value]) => {
|
Object.entries(userFilters).forEach(([key, value]) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
if (Array.isArray(value) && value.length === 0) return;
|
if (Array.isArray(value) && value.length === 0) return;
|
||||||
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
appliedFilters[key] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateRouteParams = useCallback(
|
const updateRouteParams = useCallback(
|
||||||
@ -36,16 +34,17 @@ export const IssueAppliedFilters: FC = observer(() => {
|
|||||||
const priority = key === "priority" ? value || [] : issueFilters?.filters?.priority ?? [];
|
const priority = key === "priority" ? value || [] : issueFilters?.filters?.priority ?? [];
|
||||||
const labels = key === "labels" ? value || [] : issueFilters?.filters?.labels ?? [];
|
const labels = key === "labels" ? value || [] : issueFilters?.filters?.labels ?? [];
|
||||||
|
|
||||||
let params: any = { board: activeBoard || "list" };
|
let params: any = { board: activeLayout || "list" };
|
||||||
if (!clearFields) {
|
if (!clearFields) {
|
||||||
if (priority.length > 0) params = { ...params, priorities: priority.join(",") };
|
if (priority.length > 0) params = { ...params, priorities: priority.join(",") };
|
||||||
if (state.length > 0) params = { ...params, states: state.join(",") };
|
if (state.length > 0) params = { ...params, states: state.join(",") };
|
||||||
if (labels.length > 0) params = { ...params, labels: labels.join(",") };
|
if (labels.length > 0) params = { ...params, labels: labels.join(",") };
|
||||||
}
|
}
|
||||||
|
console.log("params", params);
|
||||||
router.push({ pathname: `/${workspaceSlug}/${projectId}`, query: { ...params } }, undefined, { shallow: true });
|
// 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) => {
|
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||||
@ -80,7 +79,7 @@ export const IssueAppliedFilters: FC = observer(() => {
|
|||||||
<div className="border-b border-custom-border-200 p-5 py-3">
|
<div className="border-b border-custom-border-200 p-5 py-3">
|
||||||
<AppliedFiltersList
|
<AppliedFiltersList
|
||||||
appliedFilters={appliedFilters || {}}
|
appliedFilters={appliedFilters || {}}
|
||||||
handleRemoveFilter={handleRemoveFilter}
|
handleRemoveFilter={handleRemoveFilter as any}
|
||||||
handleRemoveAllFilters={handleRemoveAllFilters}
|
handleRemoveAllFilters={handleRemoveAllFilters}
|
||||||
labels={labels ?? []}
|
labels={labels ?? []}
|
||||||
states={states ?? []}
|
states={states ?? []}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FC, useCallback } from "react";
|
import { FC, useCallback } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
import { useMobxStore } from "@/hooks/store";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/store/issues/helpers";
|
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/store/issues/helpers";
|
||||||
|
@ -1,113 +1,108 @@
|
|||||||
import { useEffect } from "react";
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, FC } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
import { Briefcase } from "lucide-react";
|
import { Briefcase } from "lucide-react";
|
||||||
import { Avatar, Button } from "@plane/ui";
|
import { Avatar, Button } from "@plane/ui";
|
||||||
import { ProjectLogo } from "@/components/common";
|
import { ProjectLogo } from "@/components/common";
|
||||||
import { IssueFiltersDropdown } from "@/components/issues/filters";
|
import { IssueFiltersDropdown } from "@/components/issues/filters";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMobxStore, useUser } from "@/hooks/store";
|
import { useProject, useUser } from "@/hooks/store";
|
||||||
// store
|
// store
|
||||||
import { RootStore } from "@/store/root.store";
|
|
||||||
import { TIssueBoardKeys } from "@/types/issue";
|
|
||||||
import { NavbarIssueBoardView } from "./issue-board-view";
|
import { NavbarIssueBoardView } from "./issue-board-view";
|
||||||
import { NavbarTheme } from "./theme";
|
import { NavbarTheme } from "./theme";
|
||||||
|
|
||||||
const IssueNavbar = observer(() => {
|
type IssueNavbarProps = {
|
||||||
const {
|
projectSettings: any;
|
||||||
project: projectStore,
|
|
||||||
issuesFilter: { updateFilters },
|
|
||||||
}: RootStore = useMobxStore();
|
|
||||||
const { data: user } = useUser();
|
|
||||||
// router
|
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
|
||||||
if (workspace_slug && project_slug) {
|
const { projectSettings } = props;
|
||||||
projectStore.fetchProjectSettings(workspace_slug.toString(), project_slug.toString());
|
const { project_details, views } = projectSettings;
|
||||||
}
|
|
||||||
}, [projectStore, workspace_slug, project_slug]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
console.log("projectSettings", projectSettings);
|
||||||
if (workspace_slug && project_slug && projectStore?.deploySettings) {
|
// hooks
|
||||||
const viewsAcceptable: string[] = [];
|
const router = useRouter();
|
||||||
let currentBoard: TIssueBoardKeys | null = null;
|
// store
|
||||||
|
const { settings, activeLayout, hydrate } = useProject();
|
||||||
|
hydrate(projectSettings);
|
||||||
|
const { data: user } = useUser();
|
||||||
|
console.log("user", user);
|
||||||
|
|
||||||
if (projectStore?.deploySettings?.views?.list) viewsAcceptable.push("list");
|
// return <>layout</>;
|
||||||
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 (board) {
|
// useEffect(() => {
|
||||||
if (viewsAcceptable.includes(board.toString())) {
|
// if (workspace_slug && project_slug && settings) {
|
||||||
currentBoard = board.toString() as TIssueBoardKeys;
|
// const viewsAcceptable: string[] = [];
|
||||||
} else {
|
// let currentBoard: TIssueBoardKeys | null = null;
|
||||||
if (viewsAcceptable && viewsAcceptable.length > 0) {
|
|
||||||
currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (viewsAcceptable && viewsAcceptable.length > 0) {
|
|
||||||
currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentBoard) {
|
// if (settings?.views?.list) viewsAcceptable.push("list");
|
||||||
if (projectStore?.activeBoard === null || projectStore?.activeBoard !== currentBoard) {
|
// if (settings?.views?.kanban) viewsAcceptable.push("kanban");
|
||||||
let params: any = { board: currentBoard };
|
// if (settings?.views?.calendar) viewsAcceptable.push("calendar");
|
||||||
if (peekId && peekId.length > 0) params = { ...params, peekId: peekId };
|
// if (settings?.views?.gantt) viewsAcceptable.push("gantt");
|
||||||
if (priorities && priorities.length > 0) params = { ...params, priorities: priorities };
|
// if (settings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet");
|
||||||
if (states && states.length > 0) params = { ...params, states: states };
|
|
||||||
if (labels && labels.length > 0) params = { ...params, labels: labels };
|
|
||||||
|
|
||||||
let storeParams: any = {};
|
// if (board) {
|
||||||
if (priorities && priorities.length > 0) storeParams = { ...storeParams, priority: priorities.split(",") };
|
// if (viewsAcceptable.includes(board.toString())) {
|
||||||
if (states && states.length > 0) storeParams = { ...storeParams, state: states.split(",") };
|
// currentBoard = board.toString() as TIssueBoardKeys;
|
||||||
if (labels && labels.length > 0) storeParams = { ...storeParams, labels: labels.split(",") };
|
// } else {
|
||||||
|
// if (viewsAcceptable && viewsAcceptable.length > 0) {
|
||||||
|
// currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// if (viewsAcceptable && viewsAcceptable.length > 0) {
|
||||||
|
// currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
if (storeParams) updateFilters(project_slug, storeParams);
|
// 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 };
|
||||||
|
|
||||||
projectStore.setActiveBoard(currentBoard);
|
// let storeParams: any = {};
|
||||||
router.push({
|
// if (priorities && priorities.length > 0) storeParams = { ...storeParams, priority: priorities.split(",") };
|
||||||
pathname: `/${workspace_slug}/${project_slug}`,
|
// if (states && states.length > 0) storeParams = { ...storeParams, state: states.split(",") };
|
||||||
query: { ...params },
|
// if (labels && labels.length > 0) storeParams = { ...storeParams, labels: labels.split(",") };
|
||||||
});
|
|
||||||
}
|
// if (storeParams) updateFilters(project_slug, storeParams);
|
||||||
}
|
|
||||||
}
|
// projectStore.setActiveBoard(currentBoard);
|
||||||
}, [
|
// router.push({
|
||||||
board,
|
// pathname: `/${workspace_slug}/${project_slug}`,
|
||||||
workspace_slug,
|
// query: { ...params },
|
||||||
project_slug,
|
// });
|
||||||
router,
|
// }
|
||||||
projectStore,
|
// }
|
||||||
projectStore?.deploySettings,
|
// }
|
||||||
updateFilters,
|
// }, [
|
||||||
labels,
|
// board,
|
||||||
states,
|
// workspace_slug,
|
||||||
priorities,
|
// project_slug,
|
||||||
peekId,
|
// router,
|
||||||
]);
|
// projectStore,
|
||||||
|
// projectStore?.deploySettings,
|
||||||
|
// updateFilters,
|
||||||
|
// labels,
|
||||||
|
// states,
|
||||||
|
// priorities,
|
||||||
|
// peekId,
|
||||||
|
// ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex w-full items-center gap-4 px-5">
|
<div className="relative flex w-full items-center gap-4 px-5">
|
||||||
{/* project detail */}
|
{/* project detail */}
|
||||||
<div className="flex flex-shrink-0 items-center gap-2">
|
<div className="flex flex-shrink-0 items-center gap-2">
|
||||||
{projectStore.project ? (
|
{project_details ? (
|
||||||
<span className="h-7 w-7 flex-shrink-0 grid place-items-center">
|
<span className="h-7 w-7 flex-shrink-0 grid place-items-center">
|
||||||
<ProjectLogo logo={projectStore.project.logo_props} className="text-lg" />
|
<ProjectLogo logo={project_details.logo_props} className="text-lg" />
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||||
@ -115,16 +110,13 @@ const IssueNavbar = observer(() => {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium">
|
<div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium">
|
||||||
{projectStore?.project?.name || `...`}
|
{project_details?.name || `...`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* issue search bar */}
|
|
||||||
<div className="w-full">{/* <NavbarSearch /> */}</div>
|
|
||||||
|
|
||||||
{/* issue views */}
|
{/* issue views */}
|
||||||
<div className="relative flex flex-shrink-0 items-center gap-1 transition-all delay-150 ease-in-out">
|
<div className="relative flex flex-shrink-0 items-center gap-1 transition-all delay-150 ease-in-out">
|
||||||
<NavbarIssueBoardView />
|
<NavbarIssueBoardView layouts={views} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* issue filters */}
|
{/* issue filters */}
|
||||||
@ -137,7 +129,7 @@ const IssueNavbar = observer(() => {
|
|||||||
<NavbarTheme />
|
<NavbarTheme />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{user ? (
|
{user?.id ? (
|
||||||
<div className="flex items-center gap-2 rounded border border-custom-border-200 p-2">
|
<div className="flex items-center gap-2 rounded border border-custom-border-200 p-2">
|
||||||
<Avatar name={user?.display_name} src={user?.avatar ?? undefined} shape="square" size="sm" />
|
<Avatar name={user?.display_name} src={user?.avatar ?? undefined} shape="square" size="sm" />
|
||||||
<h6 className="text-xs font-medium">{user.display_name}</h6>
|
<h6 className="text-xs font-medium">{user.display_name}</h6>
|
||||||
|
@ -1,47 +1,49 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FC } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
// constants
|
// constants
|
||||||
import { issueViews } from "@/constants/data";
|
import { issueViews } from "@/constants/data";
|
||||||
|
// hooks
|
||||||
|
import { useProject } from "@/hooks/store";
|
||||||
// mobx
|
// mobx
|
||||||
import { useMobxStore } from "@/hooks/store";
|
import { TIssueBoardKeys } from "@/types/issue";
|
||||||
import { RootStore } from "@/store/root.store";
|
|
||||||
import { TIssueBoardKeys } from "types/issue";
|
|
||||||
|
|
||||||
export const NavbarIssueBoardView = observer(() => {
|
type NavbarIssueBoardViewProps = {
|
||||||
const {
|
layouts: Record<TIssueBoardKeys, boolean>;
|
||||||
project: { viewOptions, setActiveBoard, activeBoard },
|
};
|
||||||
}: RootStore = useMobxStore();
|
|
||||||
// router
|
export const NavbarIssueBoardView: FC<NavbarIssueBoardViewProps> = observer((props) => {
|
||||||
const router = useRouter();
|
const { layouts } = props;
|
||||||
const { workspace_slug, project_slug } = router.query as { workspace_slug: string; project_slug: string };
|
|
||||||
|
const { activeLayout, setActiveLayout } = useProject();
|
||||||
|
|
||||||
const handleCurrentBoardView = (boardView: string) => {
|
const handleCurrentBoardView = (boardView: string) => {
|
||||||
setActiveBoard(boardView as TIssueBoardKeys);
|
setActiveLayout(boardView as TIssueBoardKeys);
|
||||||
router.push(`/${workspace_slug}/${project_slug}?board=${boardView}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{viewOptions &&
|
{layouts &&
|
||||||
Object.keys(viewOptions).map((viewKey: string) => {
|
Object.keys(layouts).map((layoutKey: string) => {
|
||||||
if (viewOptions[viewKey]) {
|
if (layouts[layoutKey as TIssueBoardKeys]) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={viewKey}
|
key={layoutKey}
|
||||||
className={`flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded-sm ${
|
className={`flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded-sm ${
|
||||||
viewKey === activeBoard
|
layoutKey === activeLayout
|
||||||
? `bg-custom-background-80 text-custom-text-200`
|
? `bg-custom-background-80 text-custom-text-200`
|
||||||
: `text-custom-text-300 hover:bg-custom-background-80`
|
: `text-custom-text-300 hover:bg-custom-background-80`
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleCurrentBoardView(viewKey)}
|
onClick={() => handleCurrentBoardView(layoutKey)}
|
||||||
title={viewKey}
|
title={layoutKey}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`material-symbols-rounded text-[18px] ${
|
className={`material-symbols-rounded text-[18px] ${
|
||||||
issueViews[viewKey]?.className ? issueViews[viewKey]?.className : ``
|
issueViews[layoutKey]?.className ? issueViews[layoutKey]?.className : ``
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{issueViews[viewKey]?.icon}
|
{issueViews[layoutKey]?.icon}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
// next theme
|
// next theme
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
@ -16,7 +18,6 @@ export const NavbarTheme = observer(() => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!theme) return;
|
if (!theme) return;
|
||||||
|
|
||||||
setAppTheme(theme);
|
setAppTheme(theme);
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useRef } from "react";
|
import React, { useRef } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/navigation";
|
||||||
import { useForm, Controller } from "react-hook-form";
|
import { useForm, Controller } from "react-hook-form";
|
||||||
// components
|
// components
|
||||||
import { EditorRefApi } from "@plane/lite-text-editor";
|
import { EditorRefApi } from "@plane/lite-text-editor";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/navigation";
|
||||||
import { Tooltip } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
// ui
|
// ui
|
||||||
import { ReactionSelector } from "@/components/ui";
|
import { ReactionSelector } from "@/components/ui";
|
||||||
|
@ -13,7 +13,7 @@ import { IIssue } from "@/types/issue";
|
|||||||
type Props = {
|
type Props = {
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
issueDetails: IIssue | undefined;
|
issueDetails: IIssue | undefined;
|
||||||
workspace_slug: string;
|
workspaceSlug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FullScreenPeekView: React.FC<Props> = observer((props) => {
|
export const FullScreenPeekView: React.FC<Props> = observer((props) => {
|
||||||
|
@ -9,7 +9,7 @@ import { Icon } from "@/components/ui";
|
|||||||
import { copyTextToClipboard } from "@/helpers/string.helper";
|
import { copyTextToClipboard } from "@/helpers/string.helper";
|
||||||
// store
|
// store
|
||||||
import { useMobxStore } from "@/hooks/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";
|
import { RootStore } from "@/store/root.store";
|
||||||
// lib
|
// lib
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/navigation";
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { CommentCard, AddComment } from "@/components/issues/peek-overview";
|
import { CommentCard, AddComment } from "@/components/issues/peek-overview";
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
// lib
|
// lib
|
||||||
import { Tooltip } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
import { ReactionSelector } from "@/components/ui";
|
import { ReactionSelector } from "@/components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { groupReactions, renderEmoji } from "@/helpers/emoji.helper";
|
import { groupReactions, renderEmoji } from "@/helpers/emoji.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMobxStore, useUser } from "@/hooks/store";
|
import { useIssueDetails, useUser } from "@/hooks/store";
|
||||||
|
|
||||||
export const IssueEmojiReactions: React.FC = observer(() => {
|
type IssueEmojiReactionsProps = {
|
||||||
// router
|
workspaceSlug: string;
|
||||||
const router = useRouter();
|
projectId: string;
|
||||||
const { workspace_slug, project_slug } = router.query;
|
};
|
||||||
|
|
||||||
|
export const IssueEmojiReactions: React.FC<IssueEmojiReactionsProps> = observer((props) => {
|
||||||
|
const { workspaceSlug, projectId } = props;
|
||||||
// store
|
// store
|
||||||
const { issueDetails: issueDetailsStore } = useMobxStore();
|
const issueDetailsStore = useIssueDetails();
|
||||||
const { data: user, fetchCurrentUser } = useUser();
|
const { data: user, fetchCurrentUser } = useUser();
|
||||||
|
|
||||||
const issueId = issueDetailsStore.peekId;
|
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 userReactions = reactions?.filter((r) => r.actor_detail.id === user?.id);
|
||||||
|
|
||||||
const handleAddReaction = (reactionHex: string) => {
|
const handleAddReaction = (reactionHex: string) => {
|
||||||
if (!workspace_slug || !project_slug || !issueId) return;
|
if (!workspaceSlug || !projectId || !issueId) return;
|
||||||
|
issueDetailsStore.addIssueReaction(workspaceSlug.toString(), projectId.toString(), issueId, reactionHex);
|
||||||
issueDetailsStore.addIssueReaction(workspace_slug.toString(), project_slug.toString(), issueId, reactionHex);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveReaction = (reactionHex: string) => {
|
const handleRemoveReaction = (reactionHex: string) => {
|
||||||
if (!workspace_slug || !project_slug || !issueId) return;
|
if (!workspaceSlug || !projectId || !issueId) return;
|
||||||
|
issueDetailsStore.removeIssueReaction(workspaceSlug.toString(), projectId.toString(), issueId, reactionHex);
|
||||||
issueDetailsStore.removeIssueReaction(workspace_slug.toString(), project_slug.toString(), issueId, reactionHex);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReactionClick = (reactionHex: string) => {
|
const handleReactionClick = (reactionHex: string) => {
|
||||||
const userReaction = userReactions?.find((r) => r.actor_detail.id === user?.id && r.reaction === reactionHex);
|
const userReaction = userReactions?.find((r) => r.actor_detail.id === user?.id && r.reaction === reactionHex);
|
||||||
|
|
||||||
if (userReaction) handleRemoveReaction(reactionHex);
|
if (userReaction) handleRemoveReaction(reactionHex);
|
||||||
else handleAddReaction(reactionHex);
|
else handleAddReaction(reactionHex);
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ import { issueGroupFilter, issuePriorityFilter } from "@/constants/data";
|
|||||||
import { renderFullDate } from "@/helpers/date-time.helper";
|
import { renderFullDate } from "@/helpers/date-time.helper";
|
||||||
import { copyTextToClipboard, addSpaceIfCamelCase } from "@/helpers/string.helper";
|
import { copyTextToClipboard, addSpaceIfCamelCase } from "@/helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IPeekMode } from "@/store/issue_details";
|
import { IPeekMode } from "@/store/issue-detail.store";
|
||||||
// constants
|
// constants
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import { IIssue } from "types/issue";
|
import { IIssue } from "types/issue";
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
import { IssueEmojiReactions, IssueVotes } from "@/components/issues/peek-overview";
|
import { IssueEmojiReactions, IssueVotes } from "@/components/issues/peek-overview";
|
||||||
import { useMobxStore } from "@/hooks/store";
|
import { useProject } from "@/hooks/store";
|
||||||
|
|
||||||
export const IssueReactions: React.FC = () => {
|
type IssueReactionsProps = {
|
||||||
const { project: projectStore } = useMobxStore();
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IssueReactions: React.FC<IssueReactionsProps> = (props) => {
|
||||||
|
const { workspaceSlug, projectId } = props;
|
||||||
|
const { canVote, canReact } = useProject();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 flex items-center gap-3">
|
<div className="mt-4 flex items-center gap-3">
|
||||||
{projectStore?.deploySettings?.votes && (
|
{canVote && (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<IssueVotes />
|
<IssueVotes />
|
||||||
@ -14,9 +20,9 @@ export const IssueReactions: React.FC = () => {
|
|||||||
<div className="h-8 w-0.5 bg-custom-background-200" />
|
<div className="h-8 w-0.5 bg-custom-background-200" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{projectStore?.deploySettings?.reactions && (
|
{canReact && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<IssueEmojiReactions />
|
<IssueEmojiReactions workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { Tooltip } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
// hooks
|
// 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 [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const issueDetailsStore = useIssueDetails();
|
||||||
|
|
||||||
const { workspace_slug, project_slug } = router.query;
|
|
||||||
|
|
||||||
const { issueDetails: issueDetailsStore } = useMobxStore();
|
|
||||||
const { data: user, fetchCurrentUser } = useUser();
|
const { data: user, fetchCurrentUser } = useUser();
|
||||||
|
|
||||||
const issueId = issueDetailsStore.peekId;
|
const issueId = issueDetailsStore.peekId;
|
||||||
@ -26,16 +25,16 @@ export const IssueVotes: React.FC = observer(() => {
|
|||||||
const isDownVotedByUser = allDownVotes?.some((vote) => vote.actor === user?.id);
|
const isDownVotedByUser = allDownVotes?.some((vote) => vote.actor === user?.id);
|
||||||
|
|
||||||
const handleVote = async (e: any, voteValue: 1 | -1) => {
|
const handleVote = async (e: any, voteValue: 1 | -1) => {
|
||||||
if (!workspace_slug || !project_slug || !issueId) return;
|
if (!workspaceSlug || !projectId || !issueId) return;
|
||||||
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
|
|
||||||
const actionPerformed = votes?.find((vote) => vote.actor === user?.id && vote.vote === voteValue);
|
const actionPerformed = votes?.find((vote) => vote.actor === user?.id && vote.vote === voteValue);
|
||||||
|
|
||||||
if (actionPerformed)
|
if (actionPerformed)
|
||||||
await issueDetailsStore.removeIssueVote(workspace_slug.toString(), project_slug.toString(), issueId);
|
await issueDetailsStore.removeIssueVote(workspaceSlug.toString(), projectId.toString(), issueId);
|
||||||
else
|
else
|
||||||
await issueDetailsStore.addIssueVote(workspace_slug.toString(), project_slug.toString(), issueId, {
|
await issueDetailsStore.addIssueVote(workspaceSlug.toString(), projectId.toString(), issueId, {
|
||||||
vote: voteValue,
|
vote: voteValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,42 +1,32 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
// mobx
|
|
||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// components
|
// components
|
||||||
import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overview";
|
import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overview";
|
||||||
// lib
|
// store
|
||||||
import { useMobxStore } from "@/hooks/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
|
// states
|
||||||
const [isSidePeekOpen, setIsSidePeekOpen] = useState(false);
|
const [isSidePeekOpen, setIsSidePeekOpen] = useState(false);
|
||||||
const [isModalPeekOpen, setIsModalPeekOpen] = 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
|
// store
|
||||||
const { issueDetails: issueDetailStore, issue: issueStore } = useMobxStore();
|
const issueDetailStore = useIssueDetails();
|
||||||
|
const issueStore = useIssue();
|
||||||
|
|
||||||
const issueDetails = issueDetailStore.peekId && peekId ? issueDetailStore.details[peekId.toString()] : undefined;
|
const issueDetails = issueDetailStore.peekId && peekId ? issueDetailStore.details[peekId.toString()] : undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
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) {
|
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 = () => {
|
const handleClose = () => {
|
||||||
issueDetailStore.setPeekId(null);
|
issueDetailStore.setPeekId(null);
|
||||||
@ -45,10 +35,8 @@ export const IssuePeekOverview: React.FC = observer(() => {
|
|||||||
if (states && states.length > 0) params.states = states;
|
if (states && states.length > 0) params.states = states;
|
||||||
if (priorities && priorities.length > 0) params.priorities = priorities;
|
if (priorities && priorities.length > 0) params.priorities = priorities;
|
||||||
if (labels && labels.length > 0) params.labels = labels;
|
if (labels && labels.length > 0) params.labels = labels;
|
||||||
|
// TODO: fix this redirection
|
||||||
router.replace({ pathname: `/${workspace_slug?.toString()}/${project_slug}`, query: { ...params } }, undefined, {
|
// router.push( encodeURI(`/${workspaceSlug?.toString()}/${projectId}`, ) { pathname: `/${workspaceSlug?.toString()}/${projectId}`, query: { ...params } });
|
||||||
shallow: true,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -118,7 +106,7 @@ export const IssuePeekOverview: React.FC = observer(() => {
|
|||||||
)}
|
)}
|
||||||
{issueDetailStore.peekMode === "full" && (
|
{issueDetailStore.peekMode === "full" && (
|
||||||
<FullScreenPeekView
|
<FullScreenPeekView
|
||||||
workspace_slug={workspace_slug}
|
workspaceSlug={workspaceSlug}
|
||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
issueDetails={issueDetails}
|
issueDetails={issueDetails}
|
||||||
/>
|
/>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
// ui
|
// ui
|
||||||
@ -5,7 +7,7 @@ import { useTheme } from "next-themes";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { Spinner } from "@plane/ui";
|
import { Spinner } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { AuthRoot, UserLoggedIn } from "@/components/accounts";
|
import { AuthRoot } from "@/components/accounts";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useUser } from "@/hooks/store";
|
||||||
// images
|
// images
|
||||||
@ -17,12 +19,15 @@ export const AuthView = observer(() => {
|
|||||||
// hooks
|
// hooks
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
// store
|
// store
|
||||||
const { data: currentUser, fetchCurrentUser, isLoading } = useUser();
|
const { fetchCurrentUser, isLoading } = useUser();
|
||||||
|
|
||||||
// fetching user information
|
// fetching user information
|
||||||
const { isLoading: isSWRLoading } = useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
|
const { isLoading: isSWRLoading } = useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
|
||||||
shouldRetryOnError: false,
|
shouldRetryOnError: false,
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
|
revalidateIfStale: false,
|
||||||
|
revalidateOnReconnect: true,
|
||||||
|
errorRetryCount: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -33,9 +38,6 @@ export const AuthView = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{currentUser ? (
|
|
||||||
<UserLoggedIn />
|
|
||||||
) : (
|
|
||||||
<div className="relative w-screen h-screen overflow-hidden">
|
<div className="relative w-screen h-screen overflow-hidden">
|
||||||
<div className="absolute inset-0 z-0">
|
<div className="absolute inset-0 z-0">
|
||||||
<Image
|
<Image
|
||||||
@ -56,7 +58,6 @@ export const AuthView = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { useEffect } from "react";
|
"use client";
|
||||||
|
|
||||||
|
import { FC, useEffect } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/router";
|
import useSWR from "swr";
|
||||||
// components
|
// components
|
||||||
import { IssueCalendarView } from "@/components/issues/board-views/calendar";
|
import { IssueCalendarView } from "@/components/issues/board-views/calendar";
|
||||||
import { IssueGanttView } from "@/components/issues/board-views/gantt";
|
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 { IssueAppliedFilters } from "@/components/issues/filters/applied-filters/root";
|
||||||
import { IssuePeekOverview } from "@/components/issues/peek-overview";
|
import { IssuePeekOverview } from "@/components/issues/peek-overview";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore, useUser } from "@/hooks/store";
|
import { useIssue, useUser, useProject, useIssueDetails } from "@/hooks/store";
|
||||||
import { RootStore } from "@/store/root.store";
|
|
||||||
// assets
|
// assets
|
||||||
import SomethingWentWrongImage from "public/something-went-wrong.svg";
|
import SomethingWentWrongImage from "public/something-went-wrong.svg";
|
||||||
|
|
||||||
export const ProjectDetailsView = observer(() => {
|
type ProjectDetailsViewProps = {
|
||||||
const router = useRouter();
|
workspaceSlug: string;
|
||||||
const { workspace_slug, project_slug, states, labels, priorities, peekId } = router.query;
|
projectId: string;
|
||||||
|
params: any;
|
||||||
|
};
|
||||||
|
|
||||||
const { issue: issueStore, project: projectStore, issueDetails: issueDetailStore }: RootStore = useMobxStore();
|
export const ProjectDetailsView: FC<ProjectDetailsViewProps> = 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();
|
const { data: currentUser, fetchCurrentUser } = useUser();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -30,25 +46,14 @@ export const ProjectDetailsView = observer(() => {
|
|||||||
}, [currentUser, fetchCurrentUser]);
|
}, [currentUser, fetchCurrentUser]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspace_slug && project_slug) {
|
if (peekId && workspaceSlug && projectId) {
|
||||||
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) {
|
|
||||||
issueDetailStore.setPeekId(peekId.toString());
|
issueDetailStore.setPeekId(peekId.toString());
|
||||||
}
|
}
|
||||||
}, [peekId, issueDetailStore, project_slug, workspace_slug]);
|
}, [peekId, issueDetailStore, projectId, workspaceSlug]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full w-full overflow-hidden">
|
<div className="relative h-full w-full overflow-hidden">
|
||||||
{workspace_slug && <IssuePeekOverview />}
|
{workspaceSlug && <IssuePeekOverview />}
|
||||||
|
|
||||||
{issueStore?.loader && !issueStore.issues ? (
|
{issueStore?.loader && !issueStore.issues ? (
|
||||||
<div className="py-10 text-center text-sm text-custom-text-100">Loading...</div>
|
<div className="py-10 text-center text-sm text-custom-text-100">Loading...</div>
|
||||||
@ -67,24 +72,24 @@ export const ProjectDetailsView = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
projectStore?.activeBoard && (
|
activeLayout && (
|
||||||
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
||||||
{/* applied filters */}
|
{/* applied filters */}
|
||||||
<IssueAppliedFilters />
|
<IssueAppliedFilters />
|
||||||
|
|
||||||
{projectStore?.activeBoard === "list" && (
|
{activeLayout === "list" && (
|
||||||
<div className="relative h-full w-full overflow-y-auto">
|
<div className="relative h-full w-full overflow-y-auto">
|
||||||
<IssueListView />
|
<IssueListView />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{projectStore?.activeBoard === "kanban" && (
|
{activeLayout === "kanban" && (
|
||||||
<div className="relative mx-auto h-full w-full p-5">
|
<div className="relative mx-auto h-full w-full p-5">
|
||||||
<IssueKanbanView />
|
<IssueKanbanView />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{projectStore?.activeBoard === "calendar" && <IssueCalendarView />}
|
{activeLayout === "calendar" && <IssueCalendarView />}
|
||||||
{projectStore?.activeBoard === "spreadsheet" && <IssueSpreadsheetView />}
|
{activeLayout === "spreadsheet" && <IssueSpreadsheetView />}
|
||||||
{projectStore?.activeBoard === "gantt" && <IssueGanttView />}
|
{activeLayout === "gantt" && <IssueGanttView />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
20
space/constants/issue.ts
Normal file
20
space/constants/issue.ts
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -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 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));
|
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
export * from "./user-mobx-provider";
|
|
||||||
|
|
||||||
export * from "./use-instance";
|
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";
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
// lib
|
||||||
|
import { StoreContext } from "@/lib/app-providers";
|
||||||
// store
|
// store
|
||||||
import { StoreContext } from "@/lib/store-context";
|
|
||||||
import { IInstanceStore } from "@/store/instance.store";
|
import { IInstanceStore } from "@/store/instance.store";
|
||||||
|
|
||||||
export const useInstance = (): IInstanceStore => {
|
export const useInstance = (): IInstanceStore => {
|
||||||
const context = useContext(StoreContext);
|
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;
|
return context.instance;
|
||||||
};
|
};
|
||||||
|
11
space/hooks/store/use-issue-details.tsx
Normal file
11
space/hooks/store/use-issue-details.tsx
Normal file
@ -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;
|
||||||
|
};
|
11
space/hooks/store/use-issue-filter.ts
Normal file
11
space/hooks/store/use-issue-filter.ts
Normal file
@ -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;
|
||||||
|
};
|
11
space/hooks/store/use-issue.ts
Normal file
11
space/hooks/store/use-issue.ts
Normal file
@ -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;
|
||||||
|
};
|
11
space/hooks/store/use-project.ts
Normal file
11
space/hooks/store/use-project.ts
Normal file
@ -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;
|
||||||
|
};
|
@ -1,10 +1,11 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
// lib
|
||||||
|
import { StoreContext } from "@/lib/app-providers";
|
||||||
// store
|
// store
|
||||||
import { StoreContext } from "@/lib/store-context";
|
import { IProfileStore } from "@/store/profile.store";
|
||||||
import { IProfileStore } from "@/store/user/profile.store";
|
|
||||||
|
|
||||||
export const useUserProfile = (): IProfileStore => {
|
export const useUserProfile = (): IProfileStore => {
|
||||||
const context = useContext(StoreContext);
|
const context = useContext(StoreContext);
|
||||||
if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider");
|
if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider");
|
||||||
return context.user.userProfile;
|
return context.user.profile;
|
||||||
};
|
};
|
@ -1,7 +1,8 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
// lib
|
||||||
|
import { StoreContext } from "@/lib/app-providers";
|
||||||
// store
|
// store
|
||||||
import { StoreContext } from "@/lib/store-context";
|
import { IUserStore } from "@/store/user.store";
|
||||||
import { IUserStore } from "@/store/user";
|
|
||||||
|
|
||||||
export const useUser = (): IUserStore => {
|
export const useUser = (): IUserStore => {
|
||||||
const context = useContext(StoreContext);
|
const context = useContext(StoreContext);
|
@ -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;
|
|
||||||
};
|
|
@ -1,2 +0,0 @@
|
|||||||
export * from "./use-user";
|
|
||||||
export * from "./use-user-profile";
|
|
@ -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 }) => (
|
|
||||||
<div className="relative flex h-screen min-h-[500px] w-screen flex-col overflow-hidden">
|
|
||||||
<div className="relative flex h-[60px] flex-shrink-0 select-none items-center border-b border-custom-border-300 bg-custom-sidebar-background-100">
|
|
||||||
<IssueNavbar />
|
|
||||||
</div>
|
|
||||||
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
|
|
||||||
<a
|
|
||||||
href="https://plane.so"
|
|
||||||
className="fixed bottom-2.5 right-5 !z-[999999] flex items-center gap-1 rounded border border-custom-border-200 bg-custom-background-100 px-2 py-1 shadow-custom-shadow-2xs"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
>
|
|
||||||
<div className="relative grid h-6 w-6 place-items-center">
|
|
||||||
<Image src={planeLogo} alt="Plane logo" className="h-6 w-6" height="24" width="24" />
|
|
||||||
</div>
|
|
||||||
<div className="text-xs">
|
|
||||||
Powered by <span className="font-semibold">Plane Deploy</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default observer(ProjectLayout);
|
|
38
space/lib/app-providers.tsx
Normal file
38
space/lib/app-providers.tsx
Normal file
@ -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 (
|
||||||
|
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
||||||
|
<StoreContext.Provider value={store}>{children}</StoreContext.Provider>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -1 +0,0 @@
|
|||||||
export const init = {};
|
|
@ -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>(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 <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
|
|
||||||
};
|
|
@ -1,6 +1,6 @@
|
|||||||
import { FC, ReactNode } from "react";
|
import { FC, ReactNode } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/navigation";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { Spinner } from "@plane/ui";
|
import { Spinner } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
"@plane/rich-text-editor": "*",
|
"@plane/rich-text-editor": "*",
|
||||||
"@plane/types": "*",
|
"@plane/types": "*",
|
||||||
"@plane/ui": "*",
|
"@plane/ui": "*",
|
||||||
|
"@plane/constants": "*",
|
||||||
"@sentry/nextjs": "^7.108.0",
|
"@sentry/nextjs": "^7.108.0",
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.3.4",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
@ -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 (
|
|
||||||
<div className="relative flex h-full min-h-screen w-screen items-center justify-center py-5">
|
|
||||||
<div className="max-w-[700px] space-y-5">
|
|
||||||
<div className="flex flex-col items-center gap-3 text-center">
|
|
||||||
<div className="relative h-[240px] w-[240px]">
|
|
||||||
<Image src={notFoundImage} layout="fill" alt="404- Page not found" />
|
|
||||||
</div>
|
|
||||||
<div className="text-xl font-medium">Oops! Something went wrong.</div>
|
|
||||||
<div className="text-sm text-custom-text-200">
|
|
||||||
Sorry, the page you are looking for cannot be found. It may have been removed, had its name changed, or is
|
|
||||||
temporarily unavailable.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-center text-center">
|
|
||||||
<a
|
|
||||||
href={redirectionUrl}
|
|
||||||
className="cursor-pointer select-none rounded-sm border border-gray-200 bg-gray-50 p-1.5 px-2.5 text-sm font-medium text-gray-700 transition-all hover:scale-105 hover:bg-gray-100 hover:text-gray-800"
|
|
||||||
>
|
|
||||||
Go to your Workspace
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Custom404Error;
|
|
@ -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 (
|
|
||||||
<AuthWrapper pageType={EPageTypes.AUTHENTICATED}>
|
|
||||||
<ProjectLayout>
|
|
||||||
<Head>
|
|
||||||
<title>{SITE_TITLE}</title>
|
|
||||||
</Head>
|
|
||||||
<ProjectDetailsView />
|
|
||||||
</ProjectLayout>
|
|
||||||
</AuthWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WorkspaceProjectPage;
|
|
@ -1,5 +0,0 @@
|
|||||||
const WorkspaceProjectPage = () => (
|
|
||||||
<div className="relative flex h-screen w-screen items-center justify-center text-5xl">Plane Workspace Space</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default WorkspaceProjectPage;
|
|
@ -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 (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>{SITE_TITLE}</title>
|
|
||||||
<meta property="og:site_name" content={SITE_NAME} />
|
|
||||||
<meta property="og:title" content={SITE_TITLE} />
|
|
||||||
<meta property="og:url" content={SITE_URL} />
|
|
||||||
<meta name="description" content={SITE_DESCRIPTION} />
|
|
||||||
<meta property="og:description" content={SITE_DESCRIPTION} />
|
|
||||||
<meta name="keywords" content={SITE_KEYWORDS} />
|
|
||||||
<meta name="twitter:site" content={`@${TWITTER_USER_NAME}`} />
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href={`${prefix}favicon/apple-touch-icon.png`} />
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href={`${prefix}favicon/favicon-32x32.png`} />
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href={`${prefix}favicon/favicon-16x16.png`} />
|
|
||||||
<link rel="manifest" href={`${prefix}site.webmanifest.json`} />
|
|
||||||
<link rel="shortcut icon" href={`${prefix}favicon/favicon.ico`} />
|
|
||||||
</Head>
|
|
||||||
<StoreProvider>
|
|
||||||
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
|
||||||
<ToastContextProvider>
|
|
||||||
<InstanceWrapper>
|
|
||||||
<Component {...pageProps} />
|
|
||||||
</InstanceWrapper>
|
|
||||||
</ToastContextProvider>
|
|
||||||
</ThemeProvider>
|
|
||||||
</StoreProvider>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MyApp;
|
|
@ -1,18 +0,0 @@
|
|||||||
import Document, { Html, Head, Main, NextScript } from "next/document";
|
|
||||||
|
|
||||||
class MyDocument extends Document {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Html>
|
|
||||||
<Head />
|
|
||||||
<body className="w-100 bg-custom-background-100 antialiased">
|
|
||||||
<div id="context-menu-portal" />
|
|
||||||
<Main />
|
|
||||||
<NextScript />
|
|
||||||
</body>
|
|
||||||
</Html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MyDocument;
|
|
@ -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<TForgotPasswordFormValues>({
|
|
||||||
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 (
|
|
||||||
<AuthWrapper pageType={EPageTypes.NON_AUTHENTICATED}>
|
|
||||||
<div className="relative h-screen w-full overflow-hidden">
|
|
||||||
<div className="absolute inset-0 z-0">
|
|
||||||
<Image
|
|
||||||
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
alt="Plane background pattern"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="relative z-10">
|
|
||||||
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
|
|
||||||
<div className="flex items-center gap-x-2 py-10">
|
|
||||||
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
|
|
||||||
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mx-auto h-full">
|
|
||||||
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
|
|
||||||
<div className="mx-auto flex flex-col">
|
|
||||||
<div className="text-center space-y-1 py-4 mx-auto sm:w-96">
|
|
||||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
|
||||||
Reset your password
|
|
||||||
</h3>
|
|
||||||
<p className="font-medium text-onboarding-text-400">
|
|
||||||
Enter your user account{"'"}s verified email address and we will send you a password reset link.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<form onSubmit={handleSubmit(handleForgotPassword)} className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
|
||||||
Email
|
|
||||||
</label>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="email"
|
|
||||||
rules={{
|
|
||||||
required: "Email is required",
|
|
||||||
validate: (value) => checkEmailValidity(value) || "Email is invalid",
|
|
||||||
}}
|
|
||||||
render={({ field: { value, onChange, ref } }) => (
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
type="email"
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
ref={ref}
|
|
||||||
hasError={Boolean(errors.email)}
|
|
||||||
placeholder="name@company.com"
|
|
||||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
|
||||||
disabled={resendTimerCode > 0}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{resendTimerCode > 0 && (
|
|
||||||
<p className="flex w-full items-start px-1 gap-1 text-xs font-medium text-green-700">
|
|
||||||
<CircleCheck height={12} width={12} className="mt-0.5" />
|
|
||||||
We sent the reset link to your email address
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="primary"
|
|
||||||
className="w-full"
|
|
||||||
size="lg"
|
|
||||||
disabled={!isValid}
|
|
||||||
loading={isSubmitting || resendTimerCode > 0}
|
|
||||||
>
|
|
||||||
{resendTimerCode > 0 ? `Resend in ${resendTimerCode} seconds` : "Send reset link"}
|
|
||||||
</Button>
|
|
||||||
<Link href="/" className={cn("w-full", getButtonStyling("link-neutral", "lg"))}>
|
|
||||||
Back to sign in
|
|
||||||
</Link>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AuthWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ForgotPasswordPage;
|
|
@ -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<TResetPasswordFormValues>({
|
|
||||||
...defaultValues,
|
|
||||||
email: email ? email.toString() : "",
|
|
||||||
});
|
|
||||||
const [csrfToken, setCsrfToken] = useState<string | undefined>(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 (
|
|
||||||
<AuthWrapper pageType={EPageTypes.NON_AUTHENTICATED}>
|
|
||||||
<div className="relative h-screen w-full overflow-hidden">
|
|
||||||
<div className="absolute inset-0 z-0">
|
|
||||||
<Image
|
|
||||||
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
alt="Plane background pattern"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="relative z-10">
|
|
||||||
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
|
|
||||||
<div className="flex items-center gap-x-2 py-10">
|
|
||||||
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
|
|
||||||
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mx-auto h-full">
|
|
||||||
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
|
|
||||||
<div className="mx-auto flex flex-col">
|
|
||||||
<div className="text-center space-y-1 py-4 mx-auto sm:w-96">
|
|
||||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
|
||||||
Set new password
|
|
||||||
</h3>
|
|
||||||
<p className="font-medium text-onboarding-text-400">Secure your account with a strong password</p>
|
|
||||||
</div>
|
|
||||||
<form
|
|
||||||
className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96"
|
|
||||||
method="POST"
|
|
||||||
action={`${API_BASE_URL}/auth/reset-password/${uidb64?.toString()}/${token?.toString()}/`}
|
|
||||||
>
|
|
||||||
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
|
||||||
Email
|
|
||||||
</label>
|
|
||||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
type="email"
|
|
||||||
value={resetFormData.email}
|
|
||||||
//hasError={Boolean(errors.email)}
|
|
||||||
placeholder="name@company.com"
|
|
||||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
|
|
||||||
Password
|
|
||||||
</label>
|
|
||||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
|
||||||
<Input
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
name="password"
|
|
||||||
value={resetFormData.password}
|
|
||||||
onChange={(e) => handleFormChange("password", e.target.value)}
|
|
||||||
//hasError={Boolean(errors.password)}
|
|
||||||
placeholder="Enter password"
|
|
||||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
|
||||||
minLength={8}
|
|
||||||
onFocus={() => setIsPasswordInputFocused(true)}
|
|
||||||
onBlur={() => setIsPasswordInputFocused(false)}
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
{showPassword ? (
|
|
||||||
<EyeOff
|
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
|
||||||
onClick={() => setShowPassword(false)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Eye
|
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
|
||||||
onClick={() => setShowPassword(true)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{isPasswordInputFocused && <PasswordStrengthMeter password={resetFormData.password} />}
|
|
||||||
</div>
|
|
||||||
{getPasswordStrength(resetFormData.password) >= 3 && (
|
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
|
||||||
Confirm password
|
|
||||||
</label>
|
|
||||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
|
||||||
<Input
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
name="confirm_password"
|
|
||||||
value={resetFormData.confirm_password}
|
|
||||||
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
|
|
||||||
placeholder="Confirm password"
|
|
||||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
|
||||||
/>
|
|
||||||
{showPassword ? (
|
|
||||||
<EyeOff
|
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
|
||||||
onClick={() => setShowPassword(false)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Eye
|
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
|
||||||
onClick={() => setShowPassword(true)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{!!resetFormData.confirm_password &&
|
|
||||||
resetFormData.password !== resetFormData.confirm_password && (
|
|
||||||
<span className="text-sm text-red-500">Passwords don{"'"}t match</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
|
|
||||||
Set password
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AuthWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ResetPasswordPage;
|
|
@ -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(() => (
|
|
||||||
<AuthWrapper pageType={EPageTypes.INIT}>
|
|
||||||
<AuthView />
|
|
||||||
</AuthWrapper>
|
|
||||||
));
|
|
||||||
|
|
||||||
export default Index;
|
|
@ -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 (
|
|
||||||
<AuthWrapper pageType={EPageTypes.ONBOARDING}>
|
|
||||||
<div className="flex h-full w-full">
|
|
||||||
<div className="w-full h-full overflow-auto px-6 py-10 sm:px-7 sm:py-14 md:px-14 lg:px-28">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex w-full items-center justify-between font-semibold ">
|
|
||||||
<div className="flex items-center gap-x-2">
|
|
||||||
<Image
|
|
||||||
src={`${ASSET_PREFIX}/plane-logos/blue-without-text.png`}
|
|
||||||
height={30}
|
|
||||||
width={30}
|
|
||||||
alt="Plane Logo"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="shrink-0 lg:hidden">
|
|
||||||
<div className="flex w-full shrink-0 justify-end">
|
|
||||||
<div className="flex items-center gap-x-2 pr-4">
|
|
||||||
{user?.avatar && (
|
|
||||||
<Avatar
|
|
||||||
name={user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
|
|
||||||
src={user?.avatar}
|
|
||||||
size={24}
|
|
||||||
shape="square"
|
|
||||||
fallbackBackgroundColor="#FCBE1D"
|
|
||||||
className="!text-base capitalize"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<span className="text-sm font-medium text-custom-text-200">
|
|
||||||
{user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col w-full items-center justify-center p-8 mt-14">
|
|
||||||
<div className="text-center space-y-1 py-4 mx-auto">
|
|
||||||
<h3 className="text-3xl font-bold text-onboarding-text-100">Welcome to Plane!</h3>
|
|
||||||
<p className="font-medium text-onboarding-text-400">
|
|
||||||
Let’s setup your profile, tell us a bit about yourself.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<OnBoardingForm user={user} finishOnboarding={finishOnboarding} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="hidden lg:block relative w-2/5 h-screen overflow-hidden px-6 py-10 sm:px-7 sm:py-14 md:px-14 lg:px-28">
|
|
||||||
<div className="flex w-full shrink-0 justify-end">
|
|
||||||
<div className="flex items-center gap-x-2 pr-4 z-10">
|
|
||||||
{user?.avatar && (
|
|
||||||
<Avatar
|
|
||||||
name={user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
|
|
||||||
src={user?.avatar}
|
|
||||||
size={24}
|
|
||||||
shape="square"
|
|
||||||
fallbackBackgroundColor="#FCBE1D"
|
|
||||||
className="!text-base capitalize"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<span className="text-sm font-medium text-custom-text-200">
|
|
||||||
{user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute inset-0 z-0">
|
|
||||||
<Image
|
|
||||||
src={resolvedTheme === "dark" ? ProfileSetupDark : ProfileSetup}
|
|
||||||
className="h-screen w-auto float-end object-cover"
|
|
||||||
alt="Profile setup"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AuthWrapper>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default OnBoardingPage;
|
|
@ -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 (
|
|
||||||
<AuthWrapper pageType={EPageTypes.PUBLIC}>
|
|
||||||
<div className="relative flex h-full min-h-screen w-screen items-center justify-center py-5">
|
|
||||||
<div className="max-w-[700px] space-y-5">
|
|
||||||
<div className="flex flex-col items-center gap-3 text-center">
|
|
||||||
<div className="relative h-[240px] w-[240px]">
|
|
||||||
<Image src={projectNotPublishedImage} layout="fill" alt="404- Page not found" />
|
|
||||||
</div>
|
|
||||||
<div className="text-xl font-medium">
|
|
||||||
Oops! The page you{`'`}re looking for isn{`'`}t live at the moment.
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-custom-text-200">
|
|
||||||
If this is your project, login to your workspace to adjust its visibility settings and make it public.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-center text-center">
|
|
||||||
<a
|
|
||||||
href={redirectionUrl}
|
|
||||||
className="cursor-pointer select-none rounded-sm border border-gray-200 bg-gray-50 p-1.5 px-2.5 text-sm font-medium text-gray-700 transition-all hover:scale-105 hover:bg-gray-100 hover:text-gray-800"
|
|
||||||
>
|
|
||||||
Go to your Workspace
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AuthWrapper>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default CustomProjectNotPublishedError;
|
|
@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import axios, { AxiosInstance } from "axios";
|
import axios, { AxiosInstance } from "axios";
|
||||||
// store
|
// store
|
||||||
import { rootStore } from "@/lib/store-context";
|
// import { rootStore } from "@/lib/store-context";
|
||||||
|
|
||||||
abstract class APIService {
|
abstract class APIService {
|
||||||
protected baseURL: string;
|
protected baseURL: string;
|
||||||
@ -18,14 +18,14 @@ abstract class APIService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setupInterceptors() {
|
private setupInterceptors() {
|
||||||
this.axiosInstance.interceptors.response.use(
|
// this.axiosInstance.interceptors.response.use(
|
||||||
(response) => response,
|
// (response) => response,
|
||||||
(error) => {
|
// (error) => {
|
||||||
const store = rootStore;
|
// const store = rootStore;
|
||||||
if (error.response && error.response.status === 401 && store.user.data) store.user.reset();
|
// if (error.response && error.response.status === 401 && store.user.data) store.user.reset();
|
||||||
return Promise.reject(error);
|
// return Promise.reject(error);
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
get(url: string, params = {}) {
|
get(url: string, params = {}) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||||
// services
|
// services
|
||||||
import APIService from "@/services/api.service";
|
import APIService from "@/services/api.service";
|
||||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
|
||||||
|
|
||||||
class IssueService extends APIService {
|
class IssueService extends APIService {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||||
// services
|
// services
|
||||||
import APIService from "@/services/api.service";
|
import APIService from "@/services/api.service";
|
||||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
|
||||||
|
|
||||||
class ProjectService extends APIService {
|
class ProjectService extends APIService {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -18,15 +18,18 @@ type TError = {
|
|||||||
export interface IInstanceStore {
|
export interface IInstanceStore {
|
||||||
// issues
|
// issues
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
instance: IInstance | undefined;
|
data: IInstance | NonNullable<unknown>;
|
||||||
|
config: Record<string, any>;
|
||||||
error: TError | undefined;
|
error: TError | undefined;
|
||||||
// action
|
// action
|
||||||
fetchInstanceInfo: () => Promise<void>;
|
fetchInstanceInfo: () => Promise<void>;
|
||||||
|
hydrate: (data: Record<string, unknown>, config: Record<string, unknown>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InstanceStore implements IInstanceStore {
|
export class InstanceStore implements IInstanceStore {
|
||||||
isLoading: boolean = true;
|
isLoading: boolean = true;
|
||||||
instance: IInstance | undefined = undefined;
|
data: IInstance | Record<string, any> = {};
|
||||||
|
config: Record<string, unknown> = {};
|
||||||
error: TError | undefined = undefined;
|
error: TError | undefined = undefined;
|
||||||
// services
|
// services
|
||||||
instanceService;
|
instanceService;
|
||||||
@ -35,15 +38,22 @@ export class InstanceStore implements IInstanceStore {
|
|||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// observable
|
// observable
|
||||||
isLoading: observable.ref,
|
isLoading: observable.ref,
|
||||||
instance: observable,
|
data: observable,
|
||||||
|
config: observable,
|
||||||
error: observable,
|
error: observable,
|
||||||
// actions
|
// actions
|
||||||
fetchInstanceInfo: action,
|
fetchInstanceInfo: action,
|
||||||
|
hydrate: action,
|
||||||
});
|
});
|
||||||
// services
|
// services
|
||||||
this.instanceService = new InstanceService();
|
this.instanceService = new InstanceService();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hydrate = (data: Record<string, unknown>, config: Record<string, unknown>) => {
|
||||||
|
this.data = { ...this.data, ...data };
|
||||||
|
this.config = { ...this.config, ...config };
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description fetching instance information
|
* @description fetching instance information
|
||||||
*/
|
*/
|
||||||
@ -51,10 +61,11 @@ export class InstanceStore implements IInstanceStore {
|
|||||||
try {
|
try {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.error = undefined;
|
this.error = undefined;
|
||||||
const instance = await this.instanceService.getInstanceInfo();
|
const instanceDetails = await this.instanceService.getInstanceInfo();
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.instance = instance;
|
this.data = instanceDetails.instance;
|
||||||
|
this.config = instanceDetails.config;
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
@ -55,7 +55,7 @@ export interface IIssueDetailStore {
|
|||||||
removeIssueVote: (workspaceId: string, projectId: string, issueId: string) => Promise<void>;
|
removeIssueVote: (workspaceId: string, projectId: string, issueId: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
class IssueDetailStore implements IIssueDetailStore {
|
export class IssueDetailStore implements IIssueDetailStore {
|
||||||
loader: boolean = false;
|
loader: boolean = false;
|
||||||
error: any = null;
|
error: any = null;
|
||||||
peekId: string | null = null;
|
peekId: string | null = null;
|
||||||
@ -431,5 +431,3 @@ class IssueDetailStore implements IIssueDetailStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default IssueDetailStore;
|
|
@ -1,15 +1,16 @@
|
|||||||
import { action, makeObservable, observable, runInAction, computed } from "mobx";
|
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 { RootStore } from "@/store/root.store";
|
||||||
import { IIssueFilterOptions, TIssueParams } from "./types";
|
// types
|
||||||
import { handleIssueQueryParamsByLayout } from "./helpers";
|
import { TIssueBoardKeys, IIssueFilterOptions, TIssueParams } from "@/types/issue";
|
||||||
import { IssueFilterBaseStore } from "./base-issue-filter.store";
|
|
||||||
|
|
||||||
interface IFiltersOptions {
|
interface IFiltersOptions {
|
||||||
filters: IIssueFilterOptions;
|
filters: IIssueFilterOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIssuesFilterStore {
|
export interface IIssueFilterStore {
|
||||||
// observables
|
// observables
|
||||||
projectIssueFilters: { [projectId: string]: IFiltersOptions } | undefined;
|
projectIssueFilters: { [projectId: string]: IFiltersOptions } | undefined;
|
||||||
// computed
|
// computed
|
||||||
@ -21,15 +22,13 @@ export interface IIssuesFilterStore {
|
|||||||
updateFilters: (projectId: string, filters: IIssueFilterOptions) => Promise<IFiltersOptions>;
|
updateFilters: (projectId: string, filters: IIssueFilterOptions) => Promise<IFiltersOptions>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IssuesFilterStore extends IssueFilterBaseStore implements IIssuesFilterStore {
|
export class IssueFilterStore implements IIssueFilterStore {
|
||||||
// observables
|
// observables
|
||||||
projectIssueFilters: { [projectId: string]: IFiltersOptions } | undefined = undefined;
|
projectIssueFilters: { [projectId: string]: IFiltersOptions } | undefined = undefined;
|
||||||
// root store
|
// root store
|
||||||
rootStore;
|
rootStore;
|
||||||
|
|
||||||
constructor(_rootStore: RootStore) {
|
constructor(_rootStore: RootStore) {
|
||||||
super(_rootStore);
|
|
||||||
|
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// observables
|
// observables
|
||||||
projectIssueFilters: observable.ref,
|
projectIssueFilters: observable.ref,
|
||||||
@ -43,35 +42,61 @@ export class IssuesFilterStore extends IssueFilterBaseStore implements IIssuesFi
|
|||||||
this.rootStore = _rootStore;
|
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
|
// helpers
|
||||||
issueDisplayFilters = (projectId: string) => {
|
issueDisplayFilters = (projectId: string) => {
|
||||||
if (!projectId) return undefined;
|
if (!projectId) return undefined;
|
||||||
return this.projectIssueFilters?.[projectId] || undefined;
|
return this.projectIssueFilters?.[projectId] || undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
// actions
|
handleIssueQueryParamsByLayout = (layout: TIssueBoardKeys | undefined, viewType: "issues"): TIssueParams[] | null => {
|
||||||
|
const queryParams: TIssueParams[] = [];
|
||||||
|
|
||||||
updateFilters = async (projectId: string, filters: IIssueFilterOptions) => {
|
if (!layout) return null;
|
||||||
try {
|
|
||||||
let _projectIssueFilters = { ...this.projectIssueFilters };
|
|
||||||
if (!_projectIssueFilters) _projectIssueFilters = {};
|
|
||||||
if (!_projectIssueFilters[projectId]) _projectIssueFilters[projectId] = { filters: {} };
|
|
||||||
|
|
||||||
const _filters = {
|
const layoutOptions = ISSUE_DISPLAY_FILTERS_BY_LAYOUT[viewType][layout];
|
||||||
filters: { ..._projectIssueFilters[projectId].filters },
|
|
||||||
|
// add filters query params
|
||||||
|
layoutOptions.filters.forEach((option: any) => {
|
||||||
|
queryParams.push(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
return queryParams;
|
||||||
};
|
};
|
||||||
|
|
||||||
_filters.filters = { ..._filters.filters, ...filters };
|
// actions
|
||||||
|
updateFilters = async (projectId: string, filters: IIssueFilterOptions) => {
|
||||||
|
try {
|
||||||
|
let issueFilters = { ...this.projectIssueFilters };
|
||||||
|
if (!issueFilters) issueFilters = {};
|
||||||
|
if (!issueFilters[projectId]) issueFilters[projectId] = { filters: {} };
|
||||||
|
|
||||||
_projectIssueFilters[projectId] = {
|
const newFilters = {
|
||||||
filters: _filters.filters,
|
filters: { ...issueFilters[projectId].filters },
|
||||||
|
};
|
||||||
|
|
||||||
|
newFilters.filters = { ...newFilters.filters, ...filters };
|
||||||
|
|
||||||
|
issueFilters[projectId] = {
|
||||||
|
filters: newFilters.filters,
|
||||||
};
|
};
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.projectIssueFilters = _projectIssueFilters;
|
this.projectIssueFilters = issueFilters;
|
||||||
});
|
});
|
||||||
|
|
||||||
return _filters;
|
return newFilters;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -89,7 +114,7 @@ export class IssuesFilterStore extends IssueFilterBaseStore implements IIssuesFi
|
|||||||
|
|
||||||
get appliedFilters() {
|
get appliedFilters() {
|
||||||
const userFilters = this.issueFilters;
|
const userFilters = this.issueFilters;
|
||||||
const layout = this.rootStore.project?.activeBoard;
|
const layout = this.rootStore.project?.activeLayout;
|
||||||
if (!userFilters || !layout) return undefined;
|
if (!userFilters || !layout) return undefined;
|
||||||
|
|
||||||
let filteredRouteParams: any = {
|
let filteredRouteParams: any = {
|
||||||
@ -98,7 +123,7 @@ export class IssuesFilterStore extends IssueFilterBaseStore implements IIssuesFi
|
|||||||
labels: userFilters?.filters?.labels || undefined,
|
labels: userFilters?.filters?.labels || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredParams = handleIssueQueryParamsByLayout(layout, "issues");
|
const filteredParams = this.handleIssueQueryParamsByLayout(layout, "issues");
|
||||||
if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams);
|
if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams);
|
||||||
|
|
||||||
return filteredRouteParams;
|
return filteredRouteParams;
|
@ -1,11 +1,11 @@
|
|||||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
import { observable, action, makeObservable, runInAction } from "mobx";
|
||||||
// services
|
// services
|
||||||
import IssueService from "@/services/issue.service";
|
import IssueService from "@/services/issue.service";
|
||||||
|
// types
|
||||||
|
import { IIssue, IIssueState, IIssueLabel } from "@/types/issue";
|
||||||
// store
|
// store
|
||||||
import { RootStore } from "./root.store";
|
import { RootStore } from "./root.store";
|
||||||
// types
|
|
||||||
// import { IssueDetailType, TIssueBoardKeys } from "types/issue";
|
// import { IssueDetailType, TIssueBoardKeys } from "types/issue";
|
||||||
import { IIssue, IIssueState, IIssueLabel } from "types/issue";
|
|
||||||
|
|
||||||
export interface IIssueStore {
|
export interface IIssueStore {
|
||||||
loader: boolean;
|
loader: boolean;
|
||||||
@ -26,7 +26,7 @@ export interface IIssueStore {
|
|||||||
getFilteredIssuesByState: (state: string) => IIssue[];
|
getFilteredIssuesByState: (state: string) => IIssue[];
|
||||||
}
|
}
|
||||||
|
|
||||||
class IssueStore implements IIssueStore {
|
export class IssueStore implements IIssueStore {
|
||||||
loader: boolean = false;
|
loader: boolean = false;
|
||||||
error: any | null = null;
|
error: any | null = null;
|
||||||
|
|
||||||
@ -75,13 +75,13 @@ class IssueStore implements IIssueStore {
|
|||||||
const response = await this.issueService.getPublicIssues(workspaceSlug, projectId, params);
|
const response = await this.issueService.getPublicIssues(workspaceSlug, projectId, params);
|
||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
const _states: IIssueState[] = [...response?.states];
|
const states: IIssueState[] = [...response?.states];
|
||||||
const _labels: IIssueLabel[] = [...response?.labels];
|
const labels: IIssueLabel[] = [...response?.labels];
|
||||||
const _issues: IIssue[] = [...response?.issues];
|
const issues: IIssue[] = [...response?.issues];
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.states = _states;
|
this.states = states;
|
||||||
this.labels = _labels;
|
this.labels = labels;
|
||||||
this.issues = _issues;
|
this.issues = issues;
|
||||||
this.loader = false;
|
this.loader = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -99,5 +99,3 @@ class IssueStore implements IIssueStore {
|
|||||||
getFilteredIssuesByState = (state_id: string): IIssue[] | [] =>
|
getFilteredIssuesByState = (state_id: string): IIssue[] | [] =>
|
||||||
this.issues?.filter((issue) => issue.state == state_id) || [];
|
this.issues?.filter((issue) => issue.state == state_id) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default IssueStore;
|
|
@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
@ -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;
|
|
||||||
};
|
|
@ -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;
|
|
||||||
}
|
|
@ -11,11 +11,15 @@ export interface IProjectStore {
|
|||||||
error: any | null;
|
error: any | null;
|
||||||
workspace: IWorkspace | null;
|
workspace: IWorkspace | null;
|
||||||
project: IProject | null;
|
project: IProject | null;
|
||||||
deploySettings: IProjectSettings | null;
|
settings: IProjectSettings | null;
|
||||||
viewOptions: any;
|
activeLayout: TIssueBoardKeys;
|
||||||
activeBoard: TIssueBoardKeys | null;
|
layoutOptions: Record<TIssueBoardKeys, boolean>;
|
||||||
|
canReact: boolean;
|
||||||
|
canComment: boolean;
|
||||||
|
canVote: boolean;
|
||||||
fetchProjectSettings: (workspace_slug: string, project_slug: string) => Promise<void>;
|
fetchProjectSettings: (workspace_slug: string, project_slug: string) => Promise<void>;
|
||||||
setActiveBoard: (value: TIssueBoardKeys) => void;
|
setActiveLayout: (value: TIssueBoardKeys) => void;
|
||||||
|
hydrate: (projectSettings: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProjectStore implements IProjectStore {
|
export class ProjectStore implements IProjectStore {
|
||||||
@ -24,9 +28,18 @@ export class ProjectStore implements IProjectStore {
|
|||||||
// data
|
// data
|
||||||
workspace: IWorkspace | null = null;
|
workspace: IWorkspace | null = null;
|
||||||
project: IProject | null = null;
|
project: IProject | null = null;
|
||||||
deploySettings: IProjectSettings | null = null;
|
settings: IProjectSettings | null = null;
|
||||||
viewOptions: any = null;
|
activeLayout: TIssueBoardKeys = "list";
|
||||||
activeBoard: TIssueBoardKeys | null = null;
|
layoutOptions: Record<TIssueBoardKeys, boolean> = {
|
||||||
|
list: true,
|
||||||
|
kanban: true,
|
||||||
|
calendar: false,
|
||||||
|
gantt: false,
|
||||||
|
spreadsheet: false,
|
||||||
|
};
|
||||||
|
canReact: boolean = false;
|
||||||
|
canComment: boolean = false;
|
||||||
|
canVote: boolean = false;
|
||||||
// root store
|
// root store
|
||||||
rootStore;
|
rootStore;
|
||||||
// service
|
// service
|
||||||
@ -38,14 +51,18 @@ export class ProjectStore implements IProjectStore {
|
|||||||
loader: observable,
|
loader: observable,
|
||||||
error: observable.ref,
|
error: observable.ref,
|
||||||
// observable
|
// observable
|
||||||
workspace: observable.ref,
|
workspace: observable,
|
||||||
project: observable.ref,
|
project: observable,
|
||||||
deploySettings: observable.ref,
|
settings: observable,
|
||||||
viewOptions: observable.ref,
|
layoutOptions: observable,
|
||||||
activeBoard: observable.ref,
|
activeLayout: observable.ref,
|
||||||
|
canReact: observable.ref,
|
||||||
|
canComment: observable.ref,
|
||||||
|
canVote: observable.ref,
|
||||||
// actions
|
// actions
|
||||||
fetchProjectSettings: action,
|
fetchProjectSettings: action,
|
||||||
setActiveBoard: action,
|
setActiveLayout: action,
|
||||||
|
hydrate: action,
|
||||||
// computed
|
// computed
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -53,6 +70,20 @@ export class ProjectStore implements IProjectStore {
|
|||||||
this.projectService = new ProjectService();
|
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) => {
|
fetchProjectSettings = async (workspace_slug: string, project_slug: string) => {
|
||||||
try {
|
try {
|
||||||
this.loader = true;
|
this.loader = true;
|
||||||
@ -68,8 +99,8 @@ export class ProjectStore implements IProjectStore {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.project = currentProject;
|
this.project = currentProject;
|
||||||
this.workspace = currentWorkspace;
|
this.workspace = currentWorkspace;
|
||||||
this.viewOptions = currentViewOptions;
|
this.layoutOptions = currentViewOptions;
|
||||||
this.deploySettings = currentDeploySettings;
|
this.settings = currentDeploySettings;
|
||||||
this.loader = false;
|
this.loader = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -80,8 +111,4 @@ export class ProjectStore implements IProjectStore {
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setActiveBoard = (boardValue: TIssueBoardKeys) => {
|
|
||||||
this.activeBoard = boardValue;
|
|
||||||
};
|
|
||||||
}
|
}
|
@ -1,13 +1,11 @@
|
|||||||
// mobx lite
|
|
||||||
import { enableStaticRendering } from "mobx-react-lite";
|
import { enableStaticRendering } from "mobx-react-lite";
|
||||||
// store imports
|
// store imports
|
||||||
import { IInstanceStore, InstanceStore } from "@/store/instance.store";
|
import { IInstanceStore, InstanceStore } from "@/store/instance.store";
|
||||||
import { IProjectStore, ProjectStore } from "@/store/project";
|
import { IssueDetailStore, IIssueDetailStore } from "@/store/issue-detail.store";
|
||||||
import { IUserStore, UserStore } from "@/store/user";
|
import { IssueStore, IIssueStore } from "@/store/issue.store";
|
||||||
|
import { IProjectStore, ProjectStore } from "@/store/project.store";
|
||||||
import IssueStore, { IIssueStore } from "./issue";
|
import { IUserStore, UserStore } from "@/store/user.store";
|
||||||
import IssueDetailStore, { IIssueDetailStore } from "./issue_details";
|
import { IssueFilterStore, IIssueFilterStore } from "./issue-filters.store";
|
||||||
import { IIssuesFilterStore, IssuesFilterStore } from "./issues/issue-filters.store";
|
|
||||||
import { IMentionsStore, MentionsStore } from "./mentions.store";
|
import { IMentionsStore, MentionsStore } from "./mentions.store";
|
||||||
|
|
||||||
enableStaticRendering(typeof window === "undefined");
|
enableStaticRendering(typeof window === "undefined");
|
||||||
@ -16,33 +14,36 @@ export class RootStore {
|
|||||||
instance: IInstanceStore;
|
instance: IInstanceStore;
|
||||||
user: IUserStore;
|
user: IUserStore;
|
||||||
project: IProjectStore;
|
project: IProjectStore;
|
||||||
|
|
||||||
issue: IIssueStore;
|
issue: IIssueStore;
|
||||||
issueDetails: IIssueDetailStore;
|
issueDetail: IIssueDetailStore;
|
||||||
mentionsStore: IMentionsStore;
|
mentionStore: IMentionsStore;
|
||||||
issuesFilter: IIssuesFilterStore;
|
issueFilter: IIssueFilterStore;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.instance = new InstanceStore(this);
|
this.instance = new InstanceStore(this);
|
||||||
this.user = new UserStore(this);
|
this.user = new UserStore(this);
|
||||||
|
|
||||||
this.project = new ProjectStore(this);
|
this.project = new ProjectStore(this);
|
||||||
this.issue = new IssueStore(this);
|
this.issue = new IssueStore(this);
|
||||||
this.issueDetails = new IssueDetailStore(this);
|
this.issueDetail = new IssueDetailStore(this);
|
||||||
this.mentionsStore = new MentionsStore(this);
|
this.mentionStore = new MentionsStore(this);
|
||||||
this.issuesFilter = new IssuesFilterStore(this);
|
this.issueFilter = new IssueFilterStore(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
resetOnSignOut = () => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
localStorage.setItem("theme", "system");
|
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.instance = new InstanceStore(this);
|
||||||
this.user = new UserStore(this);
|
this.user = new UserStore(this);
|
||||||
this.project = new ProjectStore(this);
|
this.project = new ProjectStore(this);
|
||||||
|
|
||||||
this.issue = new IssueStore(this);
|
this.issue = new IssueStore(this);
|
||||||
this.issueDetails = new IssueDetailStore(this);
|
this.issueDetail = new IssueDetailStore(this);
|
||||||
this.mentionsStore = new MentionsStore(this);
|
this.mentionStore = new MentionsStore(this);
|
||||||
this.issuesFilter = new IssuesFilterStore(this);
|
this.issueFilter = new IssueFilterStore(this);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@ import { action, computed, makeObservable, observable, runInAction } from "mobx"
|
|||||||
// types
|
// types
|
||||||
import { IUser } from "@plane/types";
|
import { IUser } from "@plane/types";
|
||||||
// services
|
// services
|
||||||
import { AuthService } from "@/services/authentication.service";
|
import { AuthService } from "@/services/auth.service";
|
||||||
import { UserService } from "@/services/user.service";
|
import { UserService } from "@/services/user.service";
|
||||||
// stores
|
// stores
|
||||||
import { RootStore } from "@/store/root.store";
|
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";
|
import { ActorDetail } from "@/types/issue";
|
||||||
|
|
||||||
type TUserErrorStatus = {
|
type TUserErrorStatus = {
|
||||||
@ -22,12 +22,13 @@ export interface IUserStore {
|
|||||||
error: TUserErrorStatus | undefined;
|
error: TUserErrorStatus | undefined;
|
||||||
data: IUser | undefined;
|
data: IUser | undefined;
|
||||||
// store observables
|
// store observables
|
||||||
userProfile: IProfileStore;
|
profile: IProfileStore;
|
||||||
// computed
|
// computed
|
||||||
currentActor: ActorDetail;
|
currentActor: ActorDetail;
|
||||||
// actions
|
// actions
|
||||||
fetchCurrentUser: () => Promise<IUser | undefined>;
|
fetchCurrentUser: () => Promise<IUser | undefined>;
|
||||||
updateCurrentUser: (data: Partial<IUser>) => Promise<IUser | undefined>;
|
updateCurrentUser: (data: Partial<IUser>) => Promise<IUser | undefined>;
|
||||||
|
hydrate: (data: IUser) => void;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
signOut: () => Promise<void>;
|
signOut: () => Promise<void>;
|
||||||
}
|
}
|
||||||
@ -39,14 +40,14 @@ export class UserStore implements IUserStore {
|
|||||||
error: TUserErrorStatus | undefined = undefined;
|
error: TUserErrorStatus | undefined = undefined;
|
||||||
data: IUser | undefined = undefined;
|
data: IUser | undefined = undefined;
|
||||||
// store observables
|
// store observables
|
||||||
userProfile: IProfileStore;
|
profile: IProfileStore;
|
||||||
// service
|
// service
|
||||||
userService: UserService;
|
userService: UserService;
|
||||||
authService: AuthService;
|
authService: AuthService;
|
||||||
|
|
||||||
constructor(private store: RootStore) {
|
constructor(private store: RootStore) {
|
||||||
// stores
|
// stores
|
||||||
this.userProfile = new ProfileStore(store);
|
this.profile = new ProfileStore(store);
|
||||||
// service
|
// service
|
||||||
this.userService = new UserService();
|
this.userService = new UserService();
|
||||||
this.authService = new AuthService();
|
this.authService = new AuthService();
|
||||||
@ -58,7 +59,7 @@ export class UserStore implements IUserStore {
|
|||||||
error: observable,
|
error: observable,
|
||||||
// model observables
|
// model observables
|
||||||
data: observable,
|
data: observable,
|
||||||
userProfile: observable,
|
profile: observable,
|
||||||
// computed
|
// computed
|
||||||
currentActor: computed,
|
currentActor: computed,
|
||||||
// actions
|
// actions
|
||||||
@ -94,7 +95,7 @@ export class UserStore implements IUserStore {
|
|||||||
});
|
});
|
||||||
const user = await this.userService.currentUser();
|
const user = await this.userService.currentUser();
|
||||||
if (user && user?.id) {
|
if (user && user?.id) {
|
||||||
await this.userProfile.fetchUserProfile();
|
await this.profile.fetchUserProfile();
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.data = user;
|
this.data = user;
|
||||||
this.isLoading = false;
|
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
|
* @description resets the user store
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
@ -163,7 +168,7 @@ export class UserStore implements IUserStore {
|
|||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.error = undefined;
|
this.error = undefined;
|
||||||
this.data = undefined;
|
this.data = undefined;
|
||||||
this.userProfile = new ProfileStore(this.store);
|
this.profile = new ProfileStore(this.store);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -1,12 +1,22 @@
|
|||||||
{
|
{
|
||||||
"extends": "tsconfig/nextjs.json",
|
"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"],
|
"exclude": ["node_modules"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["*"]
|
"@/*": ["*"]
|
||||||
}
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
space/types/issue-filters.d.ts
vendored
Normal file
6
space/types/issue-filters.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export interface ILayoutDisplayFiltersOptions {
|
||||||
|
filters: (keyof IIssueFilterOptions)[];
|
||||||
|
display_properties: boolean | null;
|
||||||
|
display_filters: null;
|
||||||
|
extra_options: null;
|
||||||
|
}
|
35
space/types/issue.d.ts
vendored
35
space/types/issue.d.ts
vendored
@ -170,3 +170,38 @@ export interface IssueDetailType {
|
|||||||
votes: any[];
|
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;
|
||||||
|
}
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
"@plane/rich-text-editor": "*",
|
"@plane/rich-text-editor": "*",
|
||||||
"@plane/types": "*",
|
"@plane/types": "*",
|
||||||
"@plane/ui": "*",
|
"@plane/ui": "*",
|
||||||
|
"@plane/constants": "*",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@sentry/nextjs": "^7.108.0",
|
"@sentry/nextjs": "^7.108.0",
|
||||||
"axios": "^1.1.3",
|
"axios": "^1.1.3",
|
||||||
|
23
yarn.lock
23
yarn.lock
@ -6917,7 +6917,7 @@ postcss@8.4.31:
|
|||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
source-map-js "^1.0.2"
|
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"
|
version "8.4.38"
|
||||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
|
||||||
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
|
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
|
||||||
@ -7945,8 +7945,16 @@ streamx@^2.15.0, streamx@^2.16.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
bare-events "^2.2.0"
|
bare-events "^2.2.0"
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
name string-width-cjs
|
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"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@ -8026,7 +8034,14 @@ stringify-object@^3.3.0:
|
|||||||
is-obj "^1.0.1"
|
is-obj "^1.0.1"
|
||||||
is-regexp "^1.0.0"
|
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"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
Loading…
Reference in New Issue
Block a user