-
-
-
-
-
-
-
-
-
Your Plane instance isn{"'"}t ready yet
-
Ask your Instance Admin to complete set-up first.
-
-
- Get started
-
-
-
+
+
+
+
Welcome aboard Plane!
+
+
+ Get started by setting up your instance and workspace
+
+
+
diff --git a/space/components/issues/navbar/index.tsx b/space/components/issues/navbar/index.tsx
index de8633100..b63f86a44 100644
--- a/space/components/issues/navbar/index.tsx
+++ b/space/components/issues/navbar/index.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useEffect } from "react";
import { observer } from "mobx-react-lite";
import Link from "next/link";
diff --git a/space/components/views/auth.tsx b/space/components/views/auth.tsx
index cb36a6146..29dfa5680 100644
--- a/space/components/views/auth.tsx
+++ b/space/components/views/auth.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { observer } from "mobx-react-lite";
import Image from "next/image";
// ui
@@ -17,7 +19,7 @@ export const AuthView = observer(() => {
// hooks
const { resolvedTheme } = useTheme();
// store
- const { data: currentUser, fetchCurrentUser, isLoading } = useUser();
+ const { fetchCurrentUser, isLoading, isAuthenticated } = useUser();
// fetching user information
const { isLoading: isSWRLoading } = useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
@@ -33,7 +35,7 @@ export const AuthView = observer(() => {
) : (
<>
- {currentUser ? (
+ {isAuthenticated ? (
diff --git a/space/helpers/common.helper.ts b/space/helpers/common.helper.ts
index 085b34dc2..39f664ba4 100644
--- a/space/helpers/common.helper.ts
+++ b/space/helpers/common.helper.ts
@@ -3,4 +3,9 @@ import { twMerge } from "tailwind-merge";
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? "";
+export const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL ?? "";
+export const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH ?? "";
+
+export const WEB_BASE_URL = process.env.NEXT_PUBLIC_WEB_BASE_URL ?? "";
+
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
diff --git a/space/hooks/store/index.ts b/space/hooks/store/index.ts
index 3b7ef07c9..9a48d10f2 100644
--- a/space/hooks/store/index.ts
+++ b/space/hooks/store/index.ts
@@ -1,4 +1,2 @@
-export * from "./user-mobx-provider";
-
export * from "./use-instance";
export * from "./user";
diff --git a/space/hooks/store/use-instance.ts b/space/hooks/store/use-instance.ts
index 92165e2bb..15fdaf84f 100644
--- a/space/hooks/store/use-instance.ts
+++ b/space/hooks/store/use-instance.ts
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
-import { StoreContext } from "@/lib/store-context";
+import { StoreContext } from "@/lib/app-providers";
import { IInstanceStore } from "@/store/instance.store";
export const useInstance = (): IInstanceStore => {
diff --git a/space/hooks/store/user-mobx-provider.ts b/space/hooks/store/user-mobx-provider.ts
deleted file mode 100644
index 4fbc5591f..000000000
--- a/space/hooks/store/user-mobx-provider.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { useContext } from "react";
-// store
-import { StoreContext } from "@/lib/store-context";
-import { RootStore } from "@/store/root.store";
-
-export const useMobxStore = (): RootStore => {
- const context = useContext(StoreContext);
- if (context === undefined) throw new Error("useMobxStore must be used within StoreProvider");
- return context;
-};
diff --git a/space/hooks/store/user/use-user-profile.ts b/space/hooks/store/user/use-user-profile.ts
index d7f4c5569..61bfe64f8 100644
--- a/space/hooks/store/user/use-user-profile.ts
+++ b/space/hooks/store/user/use-user-profile.ts
@@ -1,10 +1,10 @@
import { useContext } from "react";
// store
-import { StoreContext } from "@/lib/store-context";
+import { StoreContext } from "@/lib/app-providers";
import { IProfileStore } from "@/store/user/profile.store";
export const useUserProfile = (): IProfileStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider");
- return context.profile;
+ return context.user.profile;
};
diff --git a/space/hooks/store/user/use-user.ts b/space/hooks/store/user/use-user.ts
index e491d88a2..c935946f8 100644
--- a/space/hooks/store/user/use-user.ts
+++ b/space/hooks/store/user/use-user.ts
@@ -1,7 +1,8 @@
import { useContext } from "react";
+// lib
+import { StoreContext } from "@/lib/app-providers";
// store
-import { StoreContext } from "@/lib/store-context";
-import { IUserStore } from "@/store/user";
+import { IUserStore } from "@/store/user.store";
export const useUser = (): IUserStore => {
const context = useContext(StoreContext);
diff --git a/space/lib/app-providers.tsx b/space/lib/app-providers.tsx
new file mode 100644
index 000000000..389d68ab2
--- /dev/null
+++ b/space/lib/app-providers.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import { ReactNode, createContext } from "react";
+import { ThemeProvider } from "next-themes";
+// store
+import { RootStore } from "@/store/root.store";
+
+let rootStore = new RootStore();
+
+export const StoreContext = createContext(rootStore);
+
+function initializeStore(initialData = {}) {
+ const singletonRootStore = rootStore ?? new RootStore();
+ // If your page has Next.js data fetching methods that use a Mobx store, it will
+ // get hydrated here, check `pages/ssg.js` and `pages/ssr.js` for more details
+ if (initialData) {
+ singletonRootStore.hydrate(initialData);
+ }
+ // For SSG and SSR always create a new store
+ if (typeof window === "undefined") return singletonRootStore;
+ // Create the store once in the client
+ if (!rootStore) rootStore = singletonRootStore;
+ return singletonRootStore;
+}
+
+export type AppProviderProps = {
+ children: ReactNode;
+ initialState: any;
+};
+
+export const AppProvider = ({ children, initialState = {} }: AppProviderProps) => {
+ const store = initializeStore(initialState);
+ return (
+
+ {children}
+
+ );
+};
diff --git a/space/lib/index.ts b/space/lib/index.ts
deleted file mode 100644
index a10356821..000000000
--- a/space/lib/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const init = {};
diff --git a/space/lib/store-context.tsx b/space/lib/store-context.tsx
deleted file mode 100644
index 1eff1ddde..000000000
--- a/space/lib/store-context.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { ReactElement, createContext } from "react";
-// mobx store
-import { RootStore } from "@/store/root.store";
-
-export let rootStore = new RootStore();
-
-export const StoreContext = createContext
(rootStore);
-
-const initializeStore = () => {
- const singletonRootStore = rootStore ?? new RootStore();
- if (typeof window === "undefined") return singletonRootStore;
- if (!rootStore) rootStore = singletonRootStore;
- return singletonRootStore;
-};
-
-export const StoreProvider = ({ children }: { children: ReactElement }) => {
- const store = initializeStore();
- return {children};
-};
diff --git a/space/lib/wrappers/auth-wrapper.tsx b/space/lib/wrappers/auth-wrapper.tsx
index 1dad8f337..d976a5b80 100644
--- a/space/lib/wrappers/auth-wrapper.tsx
+++ b/space/lib/wrappers/auth-wrapper.tsx
@@ -1,6 +1,6 @@
import { FC, ReactNode } from "react";
import { observer } from "mobx-react-lite";
-import { useRouter } from "next/router";
+import { useRouter } from "next/navigation";
import useSWR from "swr";
import { Spinner } from "@plane/ui";
// helpers
diff --git a/space/package.json b/space/package.json
index d27a23109..a10d190d2 100644
--- a/space/package.json
+++ b/space/package.json
@@ -4,9 +4,9 @@
"private": true,
"scripts": {
"dev": "turbo run develop",
- "develop": "next dev -p 4000",
+ "develop": "next dev -p 3002",
"build": "next build",
- "start": "next start -p 4000",
+ "start": "next start",
"lint": "next lint",
"export": "next export"
},
diff --git a/space/pages/404.tsx b/space/pages/404.tsx
deleted file mode 100644
index 4591f71f8..000000000
--- a/space/pages/404.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-// next imports
-import { observer } from "mobx-react-lite";
-import Image from "next/image";
-// hooks
-import { useInstance } from "@/hooks/store";
-// images
-import notFoundImage from "public/404.svg";
-
-const Custom404Error = observer(() => {
- // hooks
- const { instance } = useInstance();
-
- const redirectionUrl = instance?.config?.app_base_url || "/";
-
- return (
-
-
-
-
-
-
-
Oops! Something went wrong.
-
- Sorry, the page you are looking for cannot be found. It may have been removed, had its name changed, or is
- temporarily unavailable.
-
-
-
-
-
-
- );
-});
-
-export default Custom404Error;
diff --git a/space/pages/[workspace_slug]/index.tsx b/space/pages/[workspace_slug]/index.tsx
deleted file mode 100644
index 635f3fdf9..000000000
--- a/space/pages/[workspace_slug]/index.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-const WorkspaceProjectPage = () => (
- Plane Workspace Space
-);
-
-export default WorkspaceProjectPage;
diff --git a/space/pages/_app.tsx b/space/pages/_app.tsx
deleted file mode 100644
index 363b61510..000000000
--- a/space/pages/_app.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import type { AppProps } from "next/app";
-import Head from "next/head";
-import { ThemeProvider } from "next-themes";
-// styles
-import "@/styles/globals.css";
-// contexts
-import { SITE_NAME, SITE_DESCRIPTION, SITE_URL, TWITTER_USER_NAME, SITE_KEYWORDS, SITE_TITLE } from "@/constants/seo";
-import { ToastContextProvider } from "@/contexts/toast.context";
-// mobx store provider
-import { StoreProvider } from "@/lib/store-context";
-// wrappers
-import { InstanceWrapper } from "@/lib/wrappers";
-
-const prefix = "/spaces/";
-
-function MyApp({ Component, pageProps }: AppProps) {
- return (
- <>
-
- {SITE_TITLE}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export default MyApp;
diff --git a/space/pages/_document.tsx b/space/pages/_document.tsx
deleted file mode 100644
index ae4455438..000000000
--- a/space/pages/_document.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import Document, { Html, Head, Main, NextScript } from "next/document";
-
-class MyDocument extends Document {
- render() {
- return (
-
-
-
-
-
-
-
-
- );
- }
-}
-
-export default MyDocument;
diff --git a/space/pages/accounts/forgot-password.tsx b/space/pages/accounts/forgot-password.tsx
deleted file mode 100644
index 494eae9d3..000000000
--- a/space/pages/accounts/forgot-password.tsx
+++ /dev/null
@@ -1,166 +0,0 @@
-import { NextPage } from "next";
-import Image from "next/image";
-import Link from "next/link";
-import { useRouter } from "next/router";
-import { useTheme } from "next-themes";
-import { Controller, useForm } from "react-hook-form";
-// icons
-import { CircleCheck } from "lucide-react";
-// ui
-import { Button, Input, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
-// helpers
-import { EPageTypes } from "@/helpers/authentication.helper";
-import { cn } from "@/helpers/common.helper";
-import { checkEmailValidity } from "@/helpers/string.helper";
-// hooks
-import useTimer from "@/hooks/use-timer";
-// wrappers
-import { AuthWrapper } from "@/lib/wrappers";
-// services
-import { AuthService } from "@/services/authentication.service";
-// images
-import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg";
-import PlaneBackgroundPattern from "public/auth/background-pattern.svg";
-import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
-
-type TForgotPasswordFormValues = {
- email: string;
-};
-
-const defaultValues: TForgotPasswordFormValues = {
- email: "",
-};
-
-// services
-const authService = new AuthService();
-
-const ForgotPasswordPage: NextPage = () => {
- // router
- const router = useRouter();
- const { email } = router.query;
- // hooks
- const { resolvedTheme } = useTheme();
- // timer
- const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(0);
-
- // form info
- const {
- control,
- formState: { errors, isSubmitting, isValid },
- handleSubmit,
- } = useForm
({
- defaultValues: {
- ...defaultValues,
- email: email?.toString() ?? "",
- },
- });
-
- const handleForgotPassword = async (formData: TForgotPasswordFormValues) => {
- await authService
- .sendResetPasswordLink({
- email: formData.email,
- })
- .then(() => {
- setToast({
- type: TOAST_TYPE.SUCCESS,
- title: "Email sent",
- message:
- "Check your inbox for a link to reset your password. If it doesn't appear within a few minutes, check your spam folder.",
- });
- setResendCodeTimer(30);
- })
- .catch((err: any) => {
- setToast({
- type: TOAST_TYPE.ERROR,
- title: "Error!",
- message: err?.error ?? "Something went wrong. Please try again.",
- });
- });
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- Reset your password
-
-
- Enter your user account{"'"}s verified email address and we will send you a password reset link.
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default ForgotPasswordPage;
diff --git a/space/pages/accounts/reset-password.tsx b/space/pages/accounts/reset-password.tsx
deleted file mode 100644
index 773acb10e..000000000
--- a/space/pages/accounts/reset-password.tsx
+++ /dev/null
@@ -1,205 +0,0 @@
-import { useEffect, useMemo, useState } from "react";
-import { NextPage } from "next";
-import Image from "next/image";
-import { useRouter } from "next/router";
-// icons
-import { useTheme } from "next-themes";
-import { Eye, EyeOff } from "lucide-react";
-// ui
-import { Button, Input } from "@plane/ui";
-// components
-import { PasswordStrengthMeter } from "@/components/accounts";
-// helpers
-import { EPageTypes } from "@/helpers/authentication.helper";
-import { API_BASE_URL } from "@/helpers/common.helper";
-import { getPasswordStrength } from "@/helpers/password.helper";
-// wrappers
-import { AuthWrapper } from "@/lib/wrappers";
-// services
-import { AuthService } from "@/services/authentication.service";
-// images
-import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg";
-import PlaneBackgroundPattern from "public/auth/background-pattern.svg";
-import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
-
-type TResetPasswordFormValues = {
- email: string;
- password: string;
- confirm_password?: string;
-};
-
-const defaultValues: TResetPasswordFormValues = {
- email: "",
- password: "",
-};
-
-// services
-const authService = new AuthService();
-
-const ResetPasswordPage: NextPage = () => {
- // router
- const router = useRouter();
- const { uidb64, token, email } = router.query;
- // states
- const [showPassword, setShowPassword] = useState(false);
- const [resetFormData, setResetFormData] = useState({
- ...defaultValues,
- email: email ? email.toString() : "",
- });
- const [csrfToken, setCsrfToken] = useState(undefined);
- const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
- // hooks
- const { resolvedTheme } = useTheme();
-
- useEffect(() => {
- if (email && !resetFormData.email) {
- setResetFormData((prev) => ({ ...prev, email: email.toString() }));
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [email]);
-
- const handleFormChange = (key: keyof TResetPasswordFormValues, value: string) =>
- setResetFormData((prev) => ({ ...prev, [key]: value }));
-
- useEffect(() => {
- if (csrfToken === undefined)
- authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token));
- }, [csrfToken]);
-
- const isButtonDisabled = useMemo(
- () =>
- !!resetFormData.password &&
- getPasswordStrength(resetFormData.password) >= 3 &&
- resetFormData.password === resetFormData.confirm_password
- ? false
- : true,
- [resetFormData]
- );
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- Set new password
-
-
Secure your account with a strong password
-
-
-
-
-
-
-
-
- );
-};
-
-export default ResetPasswordPage;
diff --git a/space/pages/index.tsx b/space/pages/index.tsx
deleted file mode 100644
index 8bba85cca..000000000
--- a/space/pages/index.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { observer } from "mobx-react-lite";
-import { NextPage } from "next";
-// components
-import { AuthView } from "@/components/views";
-// helpers
-import { EPageTypes } from "@/helpers/authentication.helper";
-// wrapper
-import { AuthWrapper } from "@/lib/wrappers";
-
-const Index: NextPage = observer(() => (
-
-
-
-));
-
-export default Index;
diff --git a/space/pages/onboarding/index.tsx b/space/pages/onboarding/index.tsx
deleted file mode 100644
index 8318f0346..000000000
--- a/space/pages/onboarding/index.tsx
+++ /dev/null
@@ -1,131 +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";
-// 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.svg";
-
-const imagePrefix = process.env.NEXT_PUBLIC_SPACE_BASE_PATH || "";
-
-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.replace(next_path.toString());
- router.replace("/");
- };
-
- return (
-
-
-
-
-
-
-
-
- {user?.avatar && (
-
- )}
-
- {user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
-
-
-
-
-
-
-
-
Welcome to Plane!
-
- Let’s setup your profile, tell us a bit about yourself.
-
-
-
-
-
-
-
-
- {user?.avatar && (
-
- )}
-
- {user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
-
-
-
-
-
-
-
-
-
- );
-});
-
-export default OnBoardingPage;
diff --git a/space/pages/project-not-published/index.tsx b/space/pages/project-not-published/index.tsx
deleted file mode 100644
index 0bd25dd6e..000000000
--- a/space/pages/project-not-published/index.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-// next imports
-import { observer } from "mobx-react-lite";
-import Image from "next/image";
-// helpers
-import { EPageTypes } from "@/helpers/authentication.helper";
-// hooks
-import { useInstance } from "@/hooks/store";
-// wrappers
-import { AuthWrapper } from "@/lib/wrappers";
-// images
-import projectNotPublishedImage from "@/public/project-not-published.svg";
-
-const CustomProjectNotPublishedError = observer(() => {
- // hooks
- const { instance } = useInstance();
-
- const redirectionUrl = instance?.config?.app_base_url || "/";
-
- return (
-
-
-
-
-
-
-
-
- Oops! The page you{`'`}re looking for isn{`'`}t live at the moment.
-
-
- If this is your project, login to your workspace to adjust its visibility settings and make it public.
-
-
-
-
-
-
-
- );
-});
-
-export default CustomProjectNotPublishedError;
diff --git a/space/public/instance/plane-takeoff.png b/space/public/instance/plane-takeoff.png
new file mode 100644
index 000000000..417ff8299
Binary files /dev/null and b/space/public/instance/plane-takeoff.png differ
diff --git a/space/services/api.service.ts b/space/services/api.service.ts
index b6d353ccc..a5fe3e93d 100644
--- a/space/services/api.service.ts
+++ b/space/services/api.service.ts
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import axios, { AxiosInstance } from "axios";
// store
-import { rootStore } from "@/lib/store-context";
+// import { rootStore } from "@/lib/store-context";
abstract class APIService {
protected baseURL: string;
@@ -18,14 +18,14 @@ abstract class APIService {
}
private setupInterceptors() {
- this.axiosInstance.interceptors.response.use(
- (response) => response,
- (error) => {
- const store = rootStore;
- if (error.response && error.response.status === 401 && store.user.data) store.user.reset();
- return Promise.reject(error);
- }
- );
+ // this.axiosInstance.interceptors.response.use(
+ // (response) => response,
+ // (error) => {
+ // const store = rootStore;
+ // if (error.response && error.response.status === 401 && store.user.data) store.user.reset();
+ // return Promise.reject(error);
+ // }
+ // );
}
get(url: string, params = {}) {
diff --git a/space/store/instance.store.ts b/space/store/instance.store.ts
index db4fd87a9..c2499d15c 100644
--- a/space/store/instance.store.ts
+++ b/space/store/instance.store.ts
@@ -18,15 +18,18 @@ type TError = {
export interface IInstanceStore {
// issues
isLoading: boolean;
- instance: IInstance | undefined;
+ data: IInstance | NonNullable;
+ config: Record;
error: TError | undefined;
// action
fetchInstanceInfo: () => Promise;
+ hydrate: (data: Record, config: Record) => void;
}
export class InstanceStore implements IInstanceStore {
isLoading: boolean = true;
- instance: IInstance | undefined = undefined;
+ data: IInstance | Record = {};
+ config: Record = {};
error: TError | undefined = undefined;
// services
instanceService;
@@ -35,15 +38,22 @@ export class InstanceStore implements IInstanceStore {
makeObservable(this, {
// observable
isLoading: observable.ref,
- instance: observable,
+ data: observable,
+ config: observable,
error: observable,
// actions
fetchInstanceInfo: action,
+ hydrate: action,
});
// services
this.instanceService = new InstanceService();
}
+ hydrate = (data: Record, config: Record) => {
+ this.data = { ...this.data, ...data };
+ this.config = { ...this.config, ...config };
+ };
+
/**
* @description fetching instance information
*/
@@ -51,10 +61,11 @@ export class InstanceStore implements IInstanceStore {
try {
this.isLoading = true;
this.error = undefined;
- const instance = await this.instanceService.getInstanceInfo();
+ const instanceDetails = await this.instanceService.getInstanceInfo();
runInAction(() => {
this.isLoading = false;
- this.instance = instance;
+ this.data = instanceDetails.instance;
+ this.config = instanceDetails.config;
});
} catch (error) {
runInAction(() => {
diff --git a/space/store/root.store.ts b/space/store/root.store.ts
index fa0a25aaf..969292ea2 100644
--- a/space/store/root.store.ts
+++ b/space/store/root.store.ts
@@ -1,52 +1,60 @@
-// mobx lite
import { enableStaticRendering } from "mobx-react-lite";
// store imports
import { IInstanceStore, InstanceStore } from "@/store/instance.store";
-import { IProjectStore, ProjectStore } from "@/store/project";
-import { IUserStore, UserStore } from "@/store/user";
-import { IProfileStore, ProfileStore } from "@/store/user/profile.store";
+import { IUserStore, UserStore } from "@/store/user.store";
-import IssueStore, { IIssueStore } from "./issue";
-import IssueDetailStore, { IIssueDetailStore } from "./issue_details";
-import { IIssuesFilterStore, IssuesFilterStore } from "./issues/issue-filters.store";
-import { IMentionsStore, MentionsStore } from "./mentions.store";
+// import { IProjectStore, ProjectStore } from "@/store/project";
+// import { IProfileStore, ProfileStore } from "@/store/user/profile.store";
+
+// import IssueStore, { IIssueStore } from "./issue";
+// import IssueDetailStore, { IIssueDetailStore } from "./issue_details";
+// import { IIssuesFilterStore, IssuesFilterStore } from "./issues/issue-filters.store";
+// import { IMentionsStore, MentionsStore } from "./mentions.store";
enableStaticRendering(typeof window === "undefined");
export class RootStore {
instance: IInstanceStore;
user: IUserStore;
- profile: IProfileStore;
- project: IProjectStore;
+ // profile: IProfileStore;
+ // project: IProjectStore;
- issue: IIssueStore;
- issueDetails: IIssueDetailStore;
- mentionsStore: IMentionsStore;
- issuesFilter: IIssuesFilterStore;
+ // issue: IIssueStore;
+ // issueDetails: IIssueDetailStore;
+ // mentionsStore: IMentionsStore;
+ // issuesFilter: IIssuesFilterStore;
constructor() {
+ // makeObservable(this, {
+ // instance: observable,
+ // });
this.instance = new InstanceStore(this);
this.user = new UserStore(this);
- this.profile = new ProfileStore(this);
- this.project = new ProjectStore(this);
+ // this.profile = new ProfileStore(this);
+ // this.project = new ProjectStore(this);
- this.issue = new IssueStore(this);
- this.issueDetails = new IssueDetailStore(this);
- this.mentionsStore = new MentionsStore(this);
- this.issuesFilter = new IssuesFilterStore(this);
+ // this.issue = new IssueStore(this);
+ // this.issueDetails = new IssueDetailStore(this);
+ // this.mentionsStore = new MentionsStore(this);
+ // this.issuesFilter = new IssuesFilterStore(this);
}
- resetOnSignOut = () => {
- localStorage.setItem("theme", "system");
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ hydrate = (data: any) => {
+ if (!data) return;
+ this.instance.hydrate(data?.instance || {}, data?.config || {});
+ this.user.hydrate(data?.user || {});
+ };
+ reset = () => {
+ localStorage.setItem("theme", "system");
this.instance = new InstanceStore(this);
this.user = new UserStore(this);
- this.profile = new ProfileStore(this);
- this.project = new ProjectStore(this);
-
- this.issue = new IssueStore(this);
- this.issueDetails = new IssueDetailStore(this);
- this.mentionsStore = new MentionsStore(this);
- this.issuesFilter = new IssuesFilterStore(this);
+ // this.profile = new ProfileStore(this);
+ // this.project = new ProjectStore(this);
+ // this.issue = new IssueStore(this);
+ // this.issueDetails = new IssueDetailStore(this);
+ // this.mentionsStore = new MentionsStore(this);
+ // this.issuesFilter = new IssuesFilterStore(this);
};
}
diff --git a/space/store/user.store.ts b/space/store/user.store.ts
new file mode 100644
index 000000000..7c37bc2c2
--- /dev/null
+++ b/space/store/user.store.ts
@@ -0,0 +1,183 @@
+import set from "lodash/set";
+import { action, computed, makeObservable, observable, runInAction } from "mobx";
+// types
+import { IUser } from "@plane/types";
+// services
+import { AuthService } from "@/services/authentication.service";
+import { UserService } from "@/services/user.service";
+// stores
+import { RootStore } from "@/store/root.store";
+import { ProfileStore, IProfileStore } from "@/store/user/profile.store";
+import { ActorDetail } from "@/types/issue";
+
+type TUserErrorStatus = {
+ status: string;
+ message: string;
+};
+
+export interface IUserStore {
+ // observables
+ isAuthenticated: boolean;
+ isLoading: boolean;
+ error: TUserErrorStatus | undefined;
+ data: IUser | undefined;
+ // store observables
+ profile: IProfileStore;
+ // computed
+ currentActor: ActorDetail;
+ // actions
+ fetchCurrentUser: () => Promise;
+ updateCurrentUser: (data: Partial) => Promise;
+ hydrate: (data: IUser) => void;
+ reset: () => void;
+ signOut: () => Promise;
+}
+
+export class UserStore implements IUserStore {
+ // observables
+ isAuthenticated: boolean = false;
+ isLoading: boolean = false;
+ error: TUserErrorStatus | undefined = undefined;
+ data: IUser | undefined = undefined;
+ // store observables
+ profile: IProfileStore;
+ // service
+ userService: UserService;
+ authService: AuthService;
+
+ constructor(private store: RootStore) {
+ // stores
+ this.profile = new ProfileStore(store);
+ // service
+ this.userService = new UserService();
+ this.authService = new AuthService();
+ // observables
+ makeObservable(this, {
+ // observables
+ isAuthenticated: observable.ref,
+ isLoading: observable.ref,
+ error: observable,
+ // model observables
+ data: observable,
+ profile: observable,
+ // computed
+ currentActor: computed,
+ // actions
+ fetchCurrentUser: action,
+ updateCurrentUser: action,
+ reset: action,
+ signOut: action,
+ });
+ }
+
+ // computed
+ get currentActor(): ActorDetail {
+ return {
+ id: this.data?.id,
+ first_name: this.data?.first_name,
+ last_name: this.data?.last_name,
+ display_name: this.data?.display_name,
+ avatar: this.data?.avatar || undefined,
+ is_bot: false,
+ };
+ }
+
+ // actions
+ /**
+ * @description fetches the current user
+ * @returns {Promise}
+ */
+ fetchCurrentUser = async (): Promise => {
+ try {
+ runInAction(() => {
+ this.isLoading = true;
+ this.error = undefined;
+ });
+ const user = await this.userService.currentUser();
+ if (user && user?.id) {
+ await this.profile.fetchUserProfile();
+ runInAction(() => {
+ this.data = user;
+ this.isLoading = false;
+ this.isAuthenticated = true;
+ });
+ } else
+ runInAction(() => {
+ this.data = user;
+ this.isLoading = false;
+ this.isAuthenticated = false;
+ });
+ return user;
+ } catch (error) {
+ runInAction(() => {
+ this.isLoading = false;
+ this.isAuthenticated = false;
+ this.error = {
+ status: "user-fetch-error",
+ message: "Failed to fetch current user",
+ };
+ });
+ throw error;
+ }
+ };
+
+ /**
+ * @description updates the current user
+ * @param data
+ * @returns {Promise}
+ */
+ updateCurrentUser = async (data: Partial): Promise => {
+ const currentUserData = this.data;
+ try {
+ if (currentUserData) {
+ Object.keys(data).forEach((key: string) => {
+ const userKey: keyof IUser = key as keyof IUser;
+ if (this.data) set(this.data, userKey, data[userKey]);
+ });
+ }
+ const user = await this.userService.updateUser(data);
+ return user;
+ } catch (error) {
+ if (currentUserData) {
+ Object.keys(currentUserData).forEach((key: string) => {
+ const userKey: keyof IUser = key as keyof IUser;
+ if (this.data) set(this.data, userKey, currentUserData[userKey]);
+ });
+ }
+ runInAction(() => {
+ this.error = {
+ status: "user-update-error",
+ message: "Failed to update current user",
+ };
+ });
+ throw error;
+ }
+ };
+
+ hydrate = (data: IUser): void => {
+ this.data = { ...this.data, ...data };
+ };
+
+ /**
+ * @description resets the user store
+ * @returns {void}
+ */
+ reset = (): void => {
+ runInAction(() => {
+ this.isAuthenticated = false;
+ this.isLoading = false;
+ this.error = undefined;
+ this.data = undefined;
+ this.profile = new ProfileStore(this.store);
+ });
+ };
+
+ /**
+ * @description signs out the current user
+ * @returns {Promise}
+ */
+ signOut = async (): Promise => {
+ // await this.authService.signOut(API_BASE_URL);
+ // this.store.resetOnSignOut();
+ };
+}
diff --git a/space/tsconfig.json b/space/tsconfig.json
index 9d3e164be..f7833dff1 100644
--- a/space/tsconfig.json
+++ b/space/tsconfig.json
@@ -1,12 +1,22 @@
{
"extends": "tsconfig/nextjs.json",
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "additional.d.ts"],
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "additional.d.ts", ".next/types/**/*.ts"],
"exclude": ["node_modules"],
"compilerOptions": {
"baseUrl": ".",
"jsx": "preserve",
"paths": {
"@/*": ["*"]
- }
+ },
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
}
}