diff --git a/admin/app/ai/layout.tsx b/admin/app/ai/layout.tsx index e3fd537bc..0a0bacac1 100644 --- a/admin/app/ai/layout.tsx +++ b/admin/app/ai/layout.tsx @@ -1,9 +1,11 @@ -"use client"; - import { ReactNode } from "react"; -// layouts +import { Metadata } from "next"; import { AdminLayout } from "@/layouts/admin-layout"; +export const metadata: Metadata = { + title: "AI Settings - God Mode", +}; + export default function AILayout({ children }: { children: ReactNode }) { return {children}; } diff --git a/admin/app/authentication/layout.tsx b/admin/app/authentication/layout.tsx index 2568859db..64506ddb4 100644 --- a/admin/app/authentication/layout.tsx +++ b/admin/app/authentication/layout.tsx @@ -1,9 +1,11 @@ -"use client"; - import { ReactNode } from "react"; -// layouts +import { Metadata } from "next"; import { AdminLayout } from "@/layouts/admin-layout"; +export const metadata: Metadata = { + title: "Authentication Settings - God Mode", +}; + export default function AuthenticationLayout({ children }: { children: ReactNode }) { return {children}; } diff --git a/admin/app/email/layout.tsx b/admin/app/email/layout.tsx index 626c5d557..64f019ec9 100644 --- a/admin/app/email/layout.tsx +++ b/admin/app/email/layout.tsx @@ -1,13 +1,15 @@ -"use client"; - import { ReactNode } from "react"; -// layouts +import { Metadata } from "next"; import { AdminLayout } from "@/layouts/admin-layout"; interface EmailLayoutProps { children: ReactNode; } +export const metadata: Metadata = { + title: "Email Settings - God Mode", +}; + const EmailLayout = ({ children }: EmailLayoutProps) => {children}; export default EmailLayout; diff --git a/admin/app/general/form.tsx b/admin/app/general/form.tsx index 09079028d..5646084e2 100644 --- a/admin/app/general/form.tsx +++ b/admin/app/general/form.tsx @@ -13,7 +13,7 @@ import { ControllerInput } from "@/components/common"; import { useInstance } from "@/hooks/store"; export interface IGeneralConfigurationForm { - instance: IInstance["instance"]; + instance: IInstance; instanceAdmins: IInstanceAdmin[]; } @@ -26,15 +26,15 @@ export const GeneralConfigurationForm: FC = observer( handleSubmit, control, formState: { errors, isSubmitting }, - } = useForm>({ + } = useForm>({ defaultValues: { instance_name: instance?.instance_name, is_telemetry_enabled: instance?.is_telemetry_enabled, }, }); - const onSubmit = async (formData: Partial) => { - const payload: Partial = { ...formData }; + const onSubmit = async (formData: Partial) => { + const payload: Partial = { ...formData }; console.log("payload", payload); diff --git a/admin/app/general/layout.tsx b/admin/app/general/layout.tsx index 371264e92..fabbe3640 100644 --- a/admin/app/general/layout.tsx +++ b/admin/app/general/layout.tsx @@ -1,6 +1,5 @@ import { ReactNode } from "react"; import { Metadata } from "next"; -// components import { AdminLayout } from "@/layouts/admin-layout"; export const metadata: Metadata = { diff --git a/admin/app/general/page.tsx b/admin/app/general/page.tsx index 399482ea6..bab2a94fc 100644 --- a/admin/app/general/page.tsx +++ b/admin/app/general/page.tsx @@ -7,7 +7,7 @@ import { GeneralConfigurationForm } from "./form"; function GeneralPage() { const { instance, instanceAdmins } = useInstance(); - console.log("instance", instanceAdmins); + console.log("instance", instance); return ( <>
@@ -19,8 +19,8 @@ function GeneralPage() {
- {instance?.instance && instanceAdmins && ( - + {instance && instanceAdmins && ( + )}
diff --git a/admin/app/image/layout.tsx b/admin/app/image/layout.tsx index 039c10202..18e9343b5 100644 --- a/admin/app/image/layout.tsx +++ b/admin/app/image/layout.tsx @@ -1,11 +1,15 @@ import { ReactNode } from "react"; -// layouts +import { Metadata } from "next"; import { AdminLayout } from "@/layouts/admin-layout"; interface ImageLayoutProps { children: ReactNode; } +export const metadata: Metadata = { + title: "Images Settings - God Mode", +}; + const ImageLayout = ({ children }: ImageLayoutProps) => {children}; export default ImageLayout; diff --git a/admin/app/layout.tsx b/admin/app/layout.tsx index e2879a565..865eb23f9 100644 --- a/admin/app/layout.tsx +++ b/admin/app/layout.tsx @@ -1,40 +1,20 @@ +"use client"; + import { ReactNode } from "react"; -import { Metadata } from "next"; -// components -import { InstanceFailureView, InstanceSetupForm } from "@/components/instance"; +import { ThemeProvider } from "next-themes"; +import { SWRConfig } from "swr"; +// constants +import { SWR_CONFIG } from "@/constants/swr-config"; // helpers import { ASSET_PREFIX } from "@/helpers/common.helper"; -// layout -import { DefaultLayout } from "@/layouts/default-layout"; // lib -import { AppProvider } from "@/lib/app-providers"; +import { InstanceProvider } from "@/lib/instance-provider"; +import { StoreProvider } from "@/lib/store-provider"; +import { UserProvider } from "@/lib/user-provider"; // styles import "./globals.css"; -// services -import { InstanceService } from "@/services/instance.service"; - -const instanceService = new InstanceService(); - -export const metadata: Metadata = { - title: "Plane | Simple, extensible, open-source project management tool.", - description: - "Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.", - openGraph: { - title: "Plane | Simple, extensible, open-source project management tool.", - description: - "Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.", - url: "https://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: ReactNode }) { - const instanceDetails = await instanceService.getInstanceInfo().catch(() => null); +function RootLayout({ children }: { children: ReactNode }) { return ( @@ -45,28 +25,18 @@ export default async function RootLayout({ children }: { children: ReactNode }) - - {instanceDetails ? ( - <> - {instanceDetails?.instance?.is_setup_done ? ( - <>{children} - ) : ( - -
- -
-
- )} - - ) : ( - -
- -
-
- )} -
+ + + + + {children} + + + + ); } + +export default RootLayout; diff --git a/admin/app/page.tsx b/admin/app/page.tsx index c7e6b975e..b402fc44d 100644 --- a/admin/app/page.tsx +++ b/admin/app/page.tsx @@ -1,7 +1,26 @@ +import { Metadata } from "next"; +// components import { InstanceSignInForm } from "@/components/login"; // layouts import { DefaultLayout } from "@/layouts/default-layout"; +export const metadata: Metadata = { + title: "Plane | Simple, extensible, open-source project management tool.", + description: + "Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.", + openGraph: { + title: "Plane | Simple, extensible, open-source project management tool.", + description: + "Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.", + url: "https://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 LoginPage() { return ( diff --git a/admin/components/common/index.ts b/admin/components/common/index.ts index 005711a62..c810cac69 100644 --- a/admin/components/common/index.ts +++ b/admin/components/common/index.ts @@ -6,3 +6,4 @@ export * from "./password-strength-meter"; export * from "./banner"; export * from "./empty-state"; export * from "./logo-spinner"; +export * from "./toast"; diff --git a/admin/components/common/logo-spinner.tsx b/admin/components/common/logo-spinner.tsx index 69bb9423d..621b685b8 100644 --- a/admin/components/common/logo-spinner.tsx +++ b/admin/components/common/logo-spinner.tsx @@ -11,7 +11,7 @@ export const LogoSpinner = () => { return (
- logo + logo
); }; diff --git a/admin/components/common/password-strength-meter.tsx b/admin/components/common/password-strength-meter.tsx index 5cdba30b7..004a927b2 100644 --- a/admin/components/common/password-strength-meter.tsx +++ b/admin/components/common/password-strength-meter.tsx @@ -43,7 +43,7 @@ export const PasswordStrengthMeter: React.FC = (props: Props) => { ]; return ( -
+
{bars.map((color, index) => (
diff --git a/admin/components/common/toast.tsx b/admin/components/common/toast.tsx new file mode 100644 index 000000000..fe4983db6 --- /dev/null +++ b/admin/components/common/toast.tsx @@ -0,0 +1,11 @@ +import { useTheme } from "next-themes"; +// ui +import { Toast as ToastComponent } from "@plane/ui"; +// helpers +import { resolveGeneralTheme } from "@/helpers/common.helper"; + +export const Toast = () => { + const { theme } = useTheme(); + + return ; +}; diff --git a/admin/components/instance/instance-failure-view.tsx b/admin/components/instance/instance-failure-view.tsx index b86750031..8722929b5 100644 --- a/admin/components/instance/instance-failure-view.tsx +++ b/admin/components/instance/instance-failure-view.tsx @@ -21,7 +21,7 @@ export const InstanceFailureView: FC = () => { }; return ( -
+
Plane Logo diff --git a/admin/components/instance/setup-form.tsx b/admin/components/instance/setup-form.tsx index 2d18ea283..fb0811f2d 100644 --- a/admin/components/instance/setup-form.tsx +++ b/admin/components/instance/setup-form.tsx @@ -129,7 +129,7 @@ export const InstanceSetupForm: FC = (props) => { ); return ( -
+

@@ -155,7 +155,7 @@ export const InstanceSetupForm: FC = (props) => { > -
+
)} -
{children}
+
{children}
); diff --git a/admin/lib/app-wrapper.tsx b/admin/lib/app-wrapper.tsx deleted file mode 100644 index daf030a5c..000000000 --- a/admin/lib/app-wrapper.tsx +++ /dev/null @@ -1,36 +0,0 @@ -"use client"; - -import { FC, ReactNode, useEffect, Suspense } from "react"; -import { observer } from "mobx-react-lite"; -import { SWRConfig } from "swr"; -// ui -import { Toast } from "@plane/ui"; -// constants -import { SWR_CONFIG } from "@/constants/swr-config"; -// helpers -import { resolveGeneralTheme } from "@/helpers/common.helper"; -// hooks -import { useTheme, useUser } from "@/hooks/store"; - -interface IAppWrapper { - children: ReactNode; -} - -export const AppWrapper: FC = observer(({ children }) => { - // hooks - const { theme, isSidebarCollapsed, toggleSidebar } = useTheme(); - const { currentUser } = useUser(); - - useEffect(() => { - const localValue = localStorage && localStorage.getItem("god_mode_sidebar_collapsed"); - const localBoolValue = localValue ? (localValue === "true" ? true : false) : false; - if (isSidebarCollapsed === undefined && localBoolValue != isSidebarCollapsed) toggleSidebar(localBoolValue); - }, [isSidebarCollapsed, currentUser, toggleSidebar]); - - return ( - - - {children} - - ); -}); diff --git a/admin/lib/instance-provider.tsx b/admin/lib/instance-provider.tsx new file mode 100644 index 000000000..fbcf27d82 --- /dev/null +++ b/admin/lib/instance-provider.tsx @@ -0,0 +1,55 @@ +import { FC, ReactNode } from "react"; +import { observer } from "mobx-react-lite"; +import useSWR from "swr"; +// components +import { LogoSpinner } from "@/components/common"; +import { InstanceSetupForm, InstanceFailureView } from "@/components/instance"; +// hooks +import { useInstance } from "@/hooks/store"; +// layout +import { DefaultLayout } from "@/layouts/default-layout"; + +type InstanceProviderProps = { + children: ReactNode; +}; + +export const InstanceProvider: FC = observer((props) => { + const { children } = props; + // store hooks + const { instance, error, fetchInstanceInfo } = useInstance(); + // fetching instance details + useSWR("INSTANCE_DETAILS", () => fetchInstanceInfo(), { + revalidateOnFocus: false, + revalidateIfStale: false, + errorRetryCount: 0, + }); + + if (!instance && !error) + return ( +
+ +
+ ); + + if (error) { + return ( + +
+ +
+
+ ); + } + + if (!instance?.is_setup_done) { + return ( + +
+ +
+
+ ); + } + + return <>{children}; +}); diff --git a/admin/lib/app-providers.tsx b/admin/lib/store-provider.tsx similarity index 63% rename from admin/lib/app-providers.tsx rename to admin/lib/store-provider.tsx index c52b4455e..842513860 100644 --- a/admin/lib/app-providers.tsx +++ b/admin/lib/store-provider.tsx @@ -1,11 +1,8 @@ "use client"; import { ReactNode, createContext } from "react"; -import { ThemeProvider } from "next-themes"; // store import { RootStore } from "@/store/root.store"; -// store initialization -import { AppWrapper } from "./app-wrapper"; let rootStore = new RootStore(); @@ -25,19 +22,13 @@ function initializeStore(initialData = {}) { return singletonRootStore; } -export type AppProviderProps = { +export type StoreProviderProps = { children: ReactNode; - initialState: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + initialState?: any; }; -export const AppProvider = ({ children, initialState = {} }: AppProviderProps) => { +export const StoreProvider = ({ children, initialState = {} }: StoreProviderProps) => { const store = initializeStore(initialState); - - return ( - - - {children} - - - ); + return {children}; }; diff --git a/admin/lib/user-provider.tsx b/admin/lib/user-provider.tsx new file mode 100644 index 000000000..d8448d13e --- /dev/null +++ b/admin/lib/user-provider.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { FC, ReactNode, useEffect } from "react"; +import { observer } from "mobx-react-lite"; +import useSWR from "swr"; +// hooks +import { useInstance, useTheme, useUser } from "@/hooks/store"; + +interface IUserProvider { + children: ReactNode; +} + +export const UserProvider: FC = observer(({ children }) => { + // hooks + const { isSidebarCollapsed, toggleSidebar } = useTheme(); + const { currentUser, fetchCurrentUser } = useUser(); + const { fetchInstanceAdmins } = useInstance(); + + useSWR("CURRENT_USER", () => fetchCurrentUser(), { + shouldRetryOnError: false, + }); + useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins()); + + useEffect(() => { + const localValue = localStorage && localStorage.getItem("god_mode_sidebar_collapsed"); + const localBoolValue = localValue ? (localValue === "true" ? true : false) : false; + if (isSidebarCollapsed === undefined && localBoolValue != isSidebarCollapsed) toggleSidebar(localBoolValue); + }, [isSidebarCollapsed, currentUser, toggleSidebar]); + + return <>{children}; +}); diff --git a/admin/services/instance.service.ts b/admin/services/instance.service.ts index 13431a834..feb94ceea 100644 --- a/admin/services/instance.service.ts +++ b/admin/services/instance.service.ts @@ -1,5 +1,11 @@ // types -import type { IFormattedInstanceConfiguration, IInstance, IInstanceAdmin, IInstanceConfiguration } from "@plane/types"; +import type { + IFormattedInstanceConfiguration, + IInstance, + IInstanceAdmin, + IInstanceConfiguration, + IInstanceInfo, +} from "@plane/types"; // helpers import { API_BASE_URL } from "@/helpers/common.helper"; import { APIService } from "@/services/api.service"; @@ -9,8 +15,8 @@ export class InstanceService extends APIService { super(API_BASE_URL); } - async getInstanceInfo(): Promise { - return this.get("/api/instances/") + async getInstanceInfo(): Promise { + return this.get("/api/instances/") .then((response) => response.data) .catch((error) => { throw error?.response?.data; @@ -25,8 +31,8 @@ export class InstanceService extends APIService { }); } - async updateInstanceInfo(data: Partial): Promise { - return this.patch, IInstance["instance"]>("/api/instances/", data) + async updateInstanceInfo(data: Partial): Promise { + return this.patch, IInstance>("/api/instances/", data) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; diff --git a/admin/store/instance.store.ts b/admin/store/instance.store.ts index e168b15b6..a99cd808c 100644 --- a/admin/store/instance.store.ts +++ b/admin/store/instance.store.ts @@ -1,6 +1,13 @@ import set from "lodash/set"; import { observable, action, computed, makeObservable, runInAction } from "mobx"; -import { IInstance, IInstanceAdmin, IInstanceConfiguration, IFormattedInstanceConfiguration } from "@plane/types"; +import { + IInstance, + IInstanceAdmin, + IInstanceConfiguration, + IFormattedInstanceConfiguration, + IInstanceInfo, + IInstanceConfig, +} from "@plane/types"; // helpers import { EInstanceStatus, TInstanceStatus } from "@/helpers"; // services @@ -11,16 +18,18 @@ import { RootStore } from "@/store/root.store"; export interface IInstanceStore { // issues isLoading: boolean; + error: any; instanceStatus: TInstanceStatus | undefined; instance: IInstance | undefined; + config: IInstanceConfig | undefined; instanceAdmins: IInstanceAdmin[] | undefined; instanceConfigurations: IInstanceConfiguration[] | undefined; // computed formattedConfig: IFormattedInstanceConfiguration | undefined; // action - hydrate: (data: any) => void; - fetchInstanceInfo: () => Promise; - updateInstanceInfo: (data: Partial) => Promise; + hydrate: (data: IInstanceInfo) => void; + fetchInstanceInfo: () => Promise; + updateInstanceInfo: (data: Partial) => Promise; fetchInstanceAdmins: () => Promise; fetchInstanceConfigurations: () => Promise; updateInstanceConfigurations: (data: Partial) => Promise; @@ -28,8 +37,10 @@ export interface IInstanceStore { export class InstanceStore implements IInstanceStore { isLoading: boolean = true; + error: any = undefined; instanceStatus: TInstanceStatus | undefined = undefined; instance: IInstance | undefined = undefined; + config: IInstanceConfig | undefined = undefined; instanceAdmins: IInstanceAdmin[] | undefined = undefined; instanceConfigurations: IInstanceConfiguration[] | undefined = undefined; // service @@ -39,6 +50,7 @@ export class InstanceStore implements IInstanceStore { makeObservable(this, { // observable isLoading: observable.ref, + error: observable.ref, instanceStatus: observable, instance: observable, instanceAdmins: observable, @@ -57,8 +69,11 @@ export class InstanceStore implements IInstanceStore { this.instanceService = new InstanceService(); } - hydrate = (data: any) => { - if (data) this.instance = data; + hydrate = (data: IInstanceInfo) => { + if (data) { + this.instance = data.instance; + this.config = data.config; + } }; /** @@ -80,17 +95,22 @@ export class InstanceStore implements IInstanceStore { fetchInstanceInfo = async () => { try { if (this.instance === undefined) this.isLoading = true; - const instance = await this.instanceService.getInstanceInfo(); + this.error = undefined; + const instanceInfo = await this.instanceService.getInstanceInfo(); // handling the new user popup toggle - if (this.instance === undefined && !instance?.instance?.workspaces_exist) this.store.theme.toggleNewUserPopup(); + if (this.instance === undefined && !instanceInfo?.instance?.workspaces_exist) + this.store.theme.toggleNewUserPopup(); runInAction(() => { + console.log("instanceInfo: ", instanceInfo); this.isLoading = false; - this.instance = instance; + this.instance = instanceInfo.instance; + this.config = instanceInfo.config; }); - return instance; + return instanceInfo; } catch (error) { console.error("Error fetching the instance info"); this.isLoading = false; + this.error = { message: "Failed to fetch the instance info" }; this.instanceStatus = { status: EInstanceStatus.ERROR, }; @@ -100,10 +120,10 @@ export class InstanceStore implements IInstanceStore { /** * @description updating instance information - * @param {Partial} data + * @param {Partial} data * @returns void */ - updateInstanceInfo = async (data: Partial) => { + updateInstanceInfo = async (data: Partial) => { try { const instanceResponse = await this.instanceService.updateInstanceInfo(data); if (instanceResponse) { diff --git a/packages/types/src/instance/base.d.ts b/packages/types/src/instance/base.d.ts index efc47b15d..d51978c06 100644 --- a/packages/types/src/instance/base.d.ts +++ b/packages/types/src/instance/base.d.ts @@ -6,47 +6,51 @@ import { TInstanceAuthenticationKeys, } from "./"; +export interface IInstanceInfo { + instance: IInstance; + config: IInstanceConfig; +} + export interface IInstance { - instance: { - id: string; - created_at: string; - updated_at: string; - instance_name: string | undefined; - whitelist_emails: string | undefined; - instance_id: string | undefined; - license_key: string | undefined; - api_key: string | undefined; - version: string | undefined; - last_checked_at: string | undefined; - namespace: string | undefined; - is_telemetry_enabled: boolean; - is_support_required: boolean; - is_activated: boolean; - is_setup_done: boolean; - is_signup_screen_visited: boolean; - user_count: number | undefined; - is_verified: boolean; - created_by: string | undefined; - updated_by: string | undefined; - workspaces_exist: boolean; - }; - config: { - is_google_enabled: boolean; - is_github_enabled: boolean; - is_magic_login_enabled: boolean; - is_email_password_enabled: boolean; - github_app_name: string | undefined; - slack_client_id: string | undefined; - posthog_api_key: string | undefined; - posthog_host: string | undefined; - has_unsplash_configured: boolean; - has_openai_configured: boolean; - file_size_limit: number | undefined; - is_smtp_configured: boolean; - app_base_url: string | undefined; - space_base_url: string | undefined; - admin_base_url: string | undefined; - }; + id: string; + created_at: string; + updated_at: string; + instance_name: string | undefined; + whitelist_emails: string | undefined; + instance_id: string | undefined; + license_key: string | undefined; + api_key: string | undefined; + version: string | undefined; + last_checked_at: string | undefined; + namespace: string | undefined; + is_telemetry_enabled: boolean; + is_support_required: boolean; + is_activated: boolean; + is_setup_done: boolean; + is_signup_screen_visited: boolean; + user_count: number | undefined; + is_verified: boolean; + created_by: string | undefined; + updated_by: string | undefined; + workspaces_exist: boolean; +} + +export interface IInstanceConfig { + is_google_enabled: boolean; + is_github_enabled: boolean; + is_magic_login_enabled: boolean; + is_email_password_enabled: boolean; + github_app_name: string | undefined; + slack_client_id: string | undefined; + posthog_api_key: string | undefined; + posthog_host: string | undefined; + has_unsplash_configured: boolean; + has_openai_configured: boolean; + file_size_limit: number | undefined; + is_smtp_configured: boolean; + app_base_url: string | undefined; + space_base_url: string | undefined; + admin_base_url: string | undefined; } export interface IInstanceAdmin { diff --git a/space/app/layout.tsx b/space/app/layout.tsx index c62f29246..86e51266f 100644 --- a/space/app/layout.tsx +++ b/space/app/layout.tsx @@ -1,16 +1,11 @@ import { Metadata } from "next"; -// styles -import "@/styles/globals.css"; -// components -import { InstanceNotReady, InstanceFailureView } from "@/components/instance"; // helpers import { ASSET_PREFIX } from "@/helpers/common.helper"; -// lib -import { AppProvider } from "@/lib/app-providers"; -// services -import { InstanceService } from "@/services/instance.service"; - -const instanceService = new InstanceService(); +// components +import { InstanceProvider } from "@/lib/instance-provider"; +import { StoreProvider } from "@/lib/store-provider"; +// styles +import "@/styles/globals.css"; export const metadata: Metadata = { title: "Plane Deploy | Make your Plane boards public with one-click", @@ -27,9 +22,7 @@ export const metadata: Metadata = { }, }; -export default async function RootLayout({ children }: { children: React.ReactNode }) { - const instanceDetails = await instanceService.getInstanceInfo().catch(() => undefined); - +export default function RootLayout({ children }: { children: React.ReactNode }) { return ( @@ -40,21 +33,9 @@ export default async function RootLayout({ children }: { children: React.ReactNo - - {!instanceDetails ? ( - - ) : ( - <> - {instanceDetails.instance.is_setup_done ? ( - <>{children} - ) : ( -
- -
- )} - - )} -
+ + {children} + ); diff --git a/space/app/not-found.tsx b/space/app/not-found.tsx index 468f89a97..cae576319 100644 --- a/space/app/not-found.tsx +++ b/space/app/not-found.tsx @@ -4,7 +4,7 @@ import Image from "next/image"; // assets import UserLoggedInImage from "public/user-logged-in.svg"; -export default function InstanceNotFound() { +export default function NotFound() { return (
diff --git a/space/app/page.tsx b/space/app/page.tsx index e1522b14b..d03300a25 100644 --- a/space/app/page.tsx +++ b/space/app/page.tsx @@ -1,7 +1,5 @@ "use client"; -import { observer } from "mobx-react-lite"; -import useSWR from "swr"; // components import { UserLoggedIn } from "@/components/account"; import { LogoSpinner } from "@/components/common"; @@ -9,15 +7,8 @@ import { AuthView } from "@/components/views"; // hooks import { useUser } from "@/hooks/store"; -function HomePage() { - const { data: currentUser, fetchCurrentUser, isAuthenticated, isLoading } = useUser(); - - useSWR("CURRENT_USER", () => fetchCurrentUser(), { - errorRetryCount: 0, - revalidateIfStale: false, - revalidateOnFocus: false, - refreshWhenHidden: false, - }); +export default function HomePage() { + const { data: currentUser, isAuthenticated, isLoading } = useUser(); if (isLoading) return ; @@ -25,5 +16,3 @@ function HomePage() { return ; } - -export default observer(HomePage); diff --git a/space/components/account/auth-forms/auth-root.tsx b/space/components/account/auth-forms/auth-root.tsx index 273e5bbd5..5be59c5b6 100644 --- a/space/components/account/auth-forms/auth-root.tsx +++ b/space/components/account/auth-forms/auth-root.tsx @@ -43,7 +43,7 @@ export const AuthRoot: FC = observer(() => { const [errorInfo, setErrorInfo] = useState(undefined); const [isPasswordAutoset, setIsPasswordAutoset] = useState(true); // hooks - const { instance } = useInstance(); + const { config } = useInstance(); useEffect(() => { if (error_code) { @@ -67,11 +67,10 @@ export const AuthRoot: FC = observer(() => { } }, [error_code]); - const isSMTPConfigured = instance?.config?.is_smtp_configured || false; - const isMagicLoginEnabled = instance?.config?.is_magic_login_enabled || false; - const isEmailPasswordEnabled = instance?.config?.is_email_password_enabled || false; - const isOAuthEnabled = - (instance?.config && (instance?.config?.is_google_enabled || instance?.config?.is_github_enabled)) || false; + const isSMTPConfigured = config?.is_smtp_configured || false; + const isMagicLoginEnabled = config?.is_magic_login_enabled || false; + const isEmailPasswordEnabled = config?.is_email_password_enabled || false; + const isOAuthEnabled = (config && (config?.is_google_enabled || config?.is_github_enabled)) || false; // submit handler- email verification const handleEmailVerification = async (data: IEmailCheckData) => { diff --git a/space/components/account/oauth/oauth-options.tsx b/space/components/account/oauth/oauth-options.tsx index 779f53925..011c7f189 100644 --- a/space/components/account/oauth/oauth-options.tsx +++ b/space/components/account/oauth/oauth-options.tsx @@ -6,7 +6,7 @@ import { useInstance } from "@/hooks/store"; export const OAuthOptions: React.FC = observer(() => { // hooks - const { instance } = useInstance(); + const { config } = useInstance(); return ( <> @@ -16,12 +16,12 @@ export const OAuthOptions: React.FC = observer(() => {
- {instance?.config?.is_google_enabled && ( + {config?.is_google_enabled && (
)} - {instance?.config?.is_github_enabled && } + {config?.is_github_enabled && }
); diff --git a/space/hooks/store/use-instance.ts b/space/hooks/store/use-instance.ts index 62aa0baae..455bc5ca0 100644 --- a/space/hooks/store/use-instance.ts +++ b/space/hooks/store/use-instance.ts @@ -1,6 +1,6 @@ import { useContext } from "react"; // lib -import { StoreContext } from "@/lib/app-providers"; +import { StoreContext } from "@/lib/store-provider"; // store import { IInstanceStore } from "@/store/instance.store"; diff --git a/space/hooks/store/use-issue-details.tsx b/space/hooks/store/use-issue-details.tsx index 56ee48627..fc708fdbd 100644 --- a/space/hooks/store/use-issue-details.tsx +++ b/space/hooks/store/use-issue-details.tsx @@ -1,6 +1,6 @@ import { useContext } from "react"; // lib -import { StoreContext } from "@/lib/app-providers"; +import { StoreContext } from "@/lib/store-provider"; // store import { IIssueDetailStore } from "@/store/issue-detail.store"; diff --git a/space/hooks/store/use-issue-filter.ts b/space/hooks/store/use-issue-filter.ts index a80d9761b..c35b01c64 100644 --- a/space/hooks/store/use-issue-filter.ts +++ b/space/hooks/store/use-issue-filter.ts @@ -1,6 +1,6 @@ import { useContext } from "react"; // lib -import { StoreContext } from "@/lib/app-providers"; +import { StoreContext } from "@/lib/store-provider"; // store import { IIssueFilterStore } from "@/store/issue-filters.store"; diff --git a/space/hooks/store/use-issue.ts b/space/hooks/store/use-issue.ts index 8ccd95ac4..641f05acf 100644 --- a/space/hooks/store/use-issue.ts +++ b/space/hooks/store/use-issue.ts @@ -1,6 +1,6 @@ import { useContext } from "react"; // lib -import { StoreContext } from "@/lib/app-providers"; +import { StoreContext } from "@/lib/store-provider"; // store import { IIssueStore } from "@/store/issue.store"; diff --git a/space/hooks/store/use-project.ts b/space/hooks/store/use-project.ts index 0bc7d8f8a..cd3e28958 100644 --- a/space/hooks/store/use-project.ts +++ b/space/hooks/store/use-project.ts @@ -1,6 +1,6 @@ import { useContext } from "react"; // lib -import { StoreContext } from "@/lib/app-providers"; +import { StoreContext } from "@/lib/store-provider"; // store import { IProjectStore } from "@/store/project.store"; diff --git a/space/hooks/store/use-user-profile.ts b/space/hooks/store/use-user-profile.ts index 042f16c0d..253fec5d6 100644 --- a/space/hooks/store/use-user-profile.ts +++ b/space/hooks/store/use-user-profile.ts @@ -1,6 +1,6 @@ import { useContext } from "react"; // lib -import { StoreContext } from "@/lib/app-providers"; +import { StoreContext } from "@/lib/store-provider"; // store import { IProfileStore } from "@/store/profile.store"; diff --git a/space/hooks/store/use-user.ts b/space/hooks/store/use-user.ts index c935946f8..7d48e5c7f 100644 --- a/space/hooks/store/use-user.ts +++ b/space/hooks/store/use-user.ts @@ -1,6 +1,6 @@ import { useContext } from "react"; // lib -import { StoreContext } from "@/lib/app-providers"; +import { StoreContext } from "@/lib/store-provider"; // store import { IUserStore } from "@/store/user.store"; diff --git a/space/lib/instance-provider.tsx b/space/lib/instance-provider.tsx new file mode 100644 index 000000000..032bc5ae9 --- /dev/null +++ b/space/lib/instance-provider.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { ReactNode } from "react"; +import { observer } from "mobx-react-lite"; +import Image from "next/image"; +import { useTheme } from "next-themes"; +import useSWR from "swr"; +// components +import { LogoSpinner } from "@/components/common"; +import { InstanceFailureView } from "@/components/instance"; +// hooks +import { useInstance, useUser } from "@/hooks/store"; +// assets +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"; + +export const InstanceProvider = observer(({ children }: { children: ReactNode }) => { + const { fetchInstanceInfo, instance, error } = useInstance(); + const { fetchCurrentUser } = useUser(); + const { resolvedTheme } = useTheme(); + + const patternBackground = resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern; + + useSWR("INSTANCE_INFO", () => fetchInstanceInfo(), { + revalidateOnFocus: false, + revalidateIfStale: false, + errorRetryCount: 0, + }); + useSWR("CURRENT_USER", () => fetchCurrentUser()); + + if (!instance && !error) + return ( +
+ +
+ ); + + if (error) { + return ( +
+
+
+
+ Plane Logo + Plane +
+
+
+ Plane background pattern +
+
+
+ +
+
+
+
+ ); + } + + return <>{children}; +}); diff --git a/space/lib/app-providers.tsx b/space/lib/store-provider.tsx similarity index 84% rename from space/lib/app-providers.tsx rename to space/lib/store-provider.tsx index 389d68ab2..b77a981f8 100644 --- a/space/lib/app-providers.tsx +++ b/space/lib/store-provider.tsx @@ -23,12 +23,13 @@ function initializeStore(initialData = {}) { return singletonRootStore; } -export type AppProviderProps = { +export type StoreProviderProps = { children: ReactNode; - initialState: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + initialState?: any; }; -export const AppProvider = ({ children, initialState = {} }: AppProviderProps) => { +export const StoreProvider = ({ children, initialState = {} }: StoreProviderProps) => { const store = initializeStore(initialState); return ( diff --git a/space/lib/user-provider.tsx b/space/lib/user-provider.tsx new file mode 100644 index 000000000..1ac1c786c --- /dev/null +++ b/space/lib/user-provider.tsx @@ -0,0 +1,12 @@ +import { ReactNode } from "react"; +import { observer } from "mobx-react-lite"; +import useSWR from "swr"; +import { useUser } from "@/hooks/store"; + +export const UserProvider = observer(({ children }: { children: ReactNode }) => { + const { fetchCurrentUser } = useUser(); + + useSWR("CURRENT_USER", () => fetchCurrentUser()); + + return <>{children}; +}); diff --git a/space/lib/wrappers/auth-wrapper.tsx b/space/lib/wrappers/auth-wrapper.tsx deleted file mode 100644 index 840ce4ba2..000000000 --- a/space/lib/wrappers/auth-wrapper.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { FC, ReactNode } from "react"; -import { observer } from "mobx-react-lite"; -import { useRouter } from "next/navigation"; -import useSWR from "swr"; -import { Spinner } from "@plane/ui"; -// helpers -import { EPageTypes } from "@/helpers/authentication.helper"; -// hooks -import { useUser, useUserProfile } from "@/hooks/store"; - -type TAuthWrapper = { - children: ReactNode; - pageType?: EPageTypes; -}; - -export const AuthWrapper: FC = observer((props) => { - const router = useRouter(); - const { children, pageType = EPageTypes.AUTHENTICATED } = props; - // hooks - const { isLoading, data: currentUser, fetchCurrentUser } = useUser(); - const { data: currentUserProfile } = useUserProfile(); - - const { isLoading: isSWRLoading } = useSWR("INSTANCE_INFORMATION", () => fetchCurrentUser(), { - revalidateOnFocus: false, - }); - - if (isSWRLoading || isLoading) - return ( -
- -
- ); - - if (pageType === EPageTypes.PUBLIC) return <>{children}; - - if (pageType === EPageTypes.INIT) { - if (!currentUser?.id) return <>{children}; - else { - if ( - currentUserProfile && - currentUserProfile?.id && - Boolean(currentUserProfile?.onboarding_step?.profile_complete) - ) - return <>{children}; - else { - router.push(`/onboarding`); - return <>; - } - } - } - - if (pageType === EPageTypes.NON_AUTHENTICATED) { - if (!currentUser?.id) return <>{children}; - else { - if (currentUserProfile?.id && currentUserProfile?.onboarding_step?.profile_complete) { - router.push(`/`); - return <>; - } else { - router.push(`/onboarding`); - return <>; - } - } - } - - if (pageType === EPageTypes.ONBOARDING) { - if (!currentUser?.id) { - router.push(`/`); - return <>; - } else { - if (currentUserProfile?.id && currentUserProfile?.onboarding_step?.profile_complete) { - router.push(`/`); - return <>; - } else return <>{children}; - } - } - - if (pageType === EPageTypes.AUTHENTICATED) { - if (!currentUser?.id) return <>{children}; - else { - if (currentUserProfile?.id && currentUserProfile?.onboarding_step?.profile_complete) return <>{children}; - else { - router.push(`/onboarding`); - return <>; - } - } - } - - return <>{children}; -}); diff --git a/space/lib/wrappers/index.ts b/space/lib/wrappers/index.ts deleted file mode 100644 index d40c4c886..000000000 --- a/space/lib/wrappers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./auth-wrapper"; diff --git a/space/services/instance.service.ts b/space/services/instance.service.ts index 7744f1f65..a11599b0c 100644 --- a/space/services/instance.service.ts +++ b/space/services/instance.service.ts @@ -1,5 +1,5 @@ // types -import type { IInstance } from "@plane/types"; +import type { IInstanceInfo } from "@plane/types"; // helpers import { API_BASE_URL } from "@/helpers/common.helper"; // services @@ -10,7 +10,7 @@ export class InstanceService extends APIService { super(API_BASE_URL); } - async getInstanceInfo(): Promise { + async getInstanceInfo(): Promise { return this.get("/api/instances/") .then((response) => response.data) .catch((error) => { diff --git a/space/store/instance.store.ts b/space/store/instance.store.ts index 98bb4cfe0..d2e6918a3 100644 --- a/space/store/instance.store.ts +++ b/space/store/instance.store.ts @@ -1,7 +1,7 @@ import set from "lodash/set"; import { observable, action, makeObservable, runInAction } from "mobx"; // types -import { IInstance } from "@plane/types"; +import { IInstance, IInstanceConfig } from "@plane/types"; // services import { InstanceService } from "@/services/instance.service"; // store types @@ -20,6 +20,7 @@ export interface IInstanceStore { // observables isLoading: boolean; instance: IInstance | undefined; + config: IInstanceConfig | undefined; error: TError | undefined; // action fetchInstanceInfo: () => Promise; @@ -29,6 +30,7 @@ export interface IInstanceStore { export class InstanceStore implements IInstanceStore { isLoading: boolean = true; instance: IInstance | undefined = undefined; + config: IInstanceConfig | undefined = undefined; error: TError | undefined = undefined; // services instanceService; @@ -38,6 +40,7 @@ export class InstanceStore implements IInstanceStore { // observable isLoading: observable.ref, instance: observable, + config: observable, error: observable, // actions fetchInstanceInfo: action, @@ -56,10 +59,11 @@ export class InstanceStore implements IInstanceStore { try { this.isLoading = true; this.error = undefined; - const instance = await this.instanceService.getInstanceInfo(); + const instanceInfo = await this.instanceService.getInstanceInfo(); runInAction(() => { this.isLoading = false; - this.instance = instance; + this.instance = instanceInfo.instance; + this.config = instanceInfo.config; }); } catch (error) { runInAction(() => { diff --git a/web/components/account/auth-forms/auth-root.tsx b/web/components/account/auth-forms/auth-root.tsx index 8b9df7632..4e4d6be24 100644 --- a/web/components/account/auth-forms/auth-root.tsx +++ b/web/components/account/auth-forms/auth-root.tsx @@ -44,7 +44,7 @@ export const AuthRoot: FC = observer((props) => { const [errorInfo, setErrorInfo] = useState(undefined); const [isPasswordAutoset, setIsPasswordAutoset] = useState(true); // hooks - const { instance } = useInstance(); + const { config } = useInstance(); useEffect(() => { if (error_code) { @@ -68,9 +68,9 @@ export const AuthRoot: FC = observer((props) => { } }, [error_code, authMode]); - const isSMTPConfigured = instance?.config?.is_smtp_configured || false; - const isMagicLoginEnabled = instance?.config?.is_magic_login_enabled || false; - const isEmailPasswordEnabled = instance?.config?.is_email_password_enabled || false; + const isSMTPConfigured = config?.is_smtp_configured || false; + const isMagicLoginEnabled = config?.is_magic_login_enabled || false; + const isEmailPasswordEnabled = config?.is_email_password_enabled || false; // submit handler- email verification const handleEmailVerification = async (data: IEmailCheckData) => { diff --git a/web/components/account/oauth/oauth-options.tsx b/web/components/account/oauth/oauth-options.tsx index 960b48adf..8541def90 100644 --- a/web/components/account/oauth/oauth-options.tsx +++ b/web/components/account/oauth/oauth-options.tsx @@ -11,10 +11,9 @@ type TOAuthOptionProps = { export const OAuthOptions: React.FC = observer((props) => { const { isSignUp = false } = props; // hooks - const { instance } = useInstance(); + const { config } = useInstance(); - const isOAuthEnabled = - (instance?.config && (instance?.config?.is_google_enabled || instance?.config?.is_github_enabled)) || false; + const isOAuthEnabled = (config && (config?.is_google_enabled || config?.is_github_enabled)) || false; if (!isOAuthEnabled) return null; @@ -28,12 +27,12 @@ export const OAuthOptions: React.FC = observer((props) => {
- {instance?.config?.is_google_enabled && ( + {config?.is_google_enabled && (
)} - {instance?.config?.is_github_enabled && } + {config?.is_github_enabled && }
); diff --git a/web/components/core/image-picker-popover.tsx b/web/components/core/image-picker-popover.tsx index 11f9e6d2d..4a080fb70 100644 --- a/web/components/core/image-picker-popover.tsx +++ b/web/components/core/image-picker-popover.tsx @@ -62,7 +62,7 @@ export const ImagePickerPopover: React.FC = observer((props) => { const router = useRouter(); const { workspaceSlug } = router.query; // store hooks - const { instance } = useInstance(); + const { config } = useInstance(); const { currentWorkspace } = useWorkspace(); const { data: unsplashImages, error: unsplashError } = useSWR( @@ -90,7 +90,7 @@ export const ImagePickerPopover: React.FC = observer((props) => { accept: { "image/*": [".png", ".jpg", ".jpeg", ".svg", ".webp"], }, - maxSize: instance?.config?.file_size_limit ?? MAX_FILE_SIZE, + maxSize: config?.file_size_limit ?? MAX_FILE_SIZE, }); const handleSubmit = async () => { diff --git a/web/components/core/modals/user-image-upload-modal.tsx b/web/components/core/modals/user-image-upload-modal.tsx index dabe3561d..03b4d4e04 100644 --- a/web/components/core/modals/user-image-upload-modal.tsx +++ b/web/components/core/modals/user-image-upload-modal.tsx @@ -30,7 +30,7 @@ export const UserImageUploadModal: React.FC = observer((props) => { const [image, setImage] = useState(null); const [isImageUploading, setIsImageUploading] = useState(false); // store hooks - const { instance } = useInstance(); + const { config } = useInstance(); const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]); @@ -39,7 +39,7 @@ export const UserImageUploadModal: React.FC = observer((props) => { accept: { "image/*": [".png", ".jpg", ".jpeg", ".svg", ".webp"], }, - maxSize: instance?.config?.file_size_limit ?? MAX_FILE_SIZE, + maxSize: config?.file_size_limit ?? MAX_FILE_SIZE, multiple: false, }); diff --git a/web/components/core/modals/workspace-image-upload-modal.tsx b/web/components/core/modals/workspace-image-upload-modal.tsx index 7c21ff814..878217780 100644 --- a/web/components/core/modals/workspace-image-upload-modal.tsx +++ b/web/components/core/modals/workspace-image-upload-modal.tsx @@ -34,7 +34,7 @@ export const WorkspaceImageUploadModal: React.FC = observer((props) => { const router = useRouter(); const { workspaceSlug } = router.query; // store hooks - const { instance } = useInstance(); + const { config } = useInstance(); const { currentWorkspace } = useWorkspace(); const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]); @@ -44,7 +44,7 @@ export const WorkspaceImageUploadModal: React.FC = observer((props) => { accept: { "image/*": [".png", ".jpg", ".jpeg", ".svg", ".webp"], }, - maxSize: instance?.config?.file_size_limit ?? MAX_FILE_SIZE, + maxSize: config?.file_size_limit ?? MAX_FILE_SIZE, multiple: false, }); diff --git a/web/components/integration/github/auth.tsx b/web/components/integration/github/auth.tsx index b4fb6bb57..ff2ee9550 100644 --- a/web/components/integration/github/auth.tsx +++ b/web/components/integration/github/auth.tsx @@ -14,12 +14,12 @@ type Props = { export const GithubAuth: React.FC = observer(({ workspaceIntegration, provider }) => { // store hooks - const { instance } = useInstance(); + const { config } = useInstance(); // hooks const { startAuth, isConnecting } = useIntegrationPopup({ provider, - github_app_name: instance?.config?.github_app_name || "", - slack_client_id: instance?.config?.slack_client_id || "", + github_app_name: config?.github_app_name || "", + slack_client_id: config?.slack_client_id || "", }); return ( diff --git a/web/components/integration/single-integration-card.tsx b/web/components/integration/single-integration-card.tsx index c629e7b04..2697bf6cd 100644 --- a/web/components/integration/single-integration-card.tsx +++ b/web/components/integration/single-integration-card.tsx @@ -46,7 +46,7 @@ export const SingleIntegrationCard: React.FC = observer(({ integration }) const router = useRouter(); const { workspaceSlug } = router.query; // store hooks - const { instance } = useInstance(); + const { config } = useInstance(); const { membership: { currentWorkspaceRole }, } = useUser(); @@ -55,8 +55,8 @@ export const SingleIntegrationCard: React.FC = observer(({ integration }) const { isMobile } = usePlatformOS(); const { startAuth, isConnecting: isInstalling } = useIntegrationPopup({ provider: integration.provider, - github_app_name: instance?.config?.github_app_name || "", - slack_client_id: instance?.config?.slack_client_id || "", + github_app_name: config?.github_app_name || "", + slack_client_id: config?.slack_client_id || "", }); const { data: workspaceIntegrations } = useSWR( diff --git a/web/components/integration/slack/select-channel.tsx b/web/components/integration/slack/select-channel.tsx index e2c5c0e6a..19275fc1f 100644 --- a/web/components/integration/slack/select-channel.tsx +++ b/web/components/integration/slack/select-channel.tsx @@ -22,7 +22,7 @@ const appInstallationService = new AppInstallationService(); export const SelectChannel: React.FC = observer(({ integration }) => { // store hooks - const { instance } = useInstance(); + const { config } = useInstance(); // states const [slackChannelAvailabilityToggle, setSlackChannelAvailabilityToggle] = useState(false); const [slackChannel, setSlackChannel] = useState(null); @@ -35,7 +35,7 @@ export const SelectChannel: React.FC = observer(({ integration }) => { provider: "slackChannel", stateParams: integration.id, // github_app_name: instance?.config?.github_client_id || "", - slack_client_id: instance?.config?.slack_client_id || "", + slack_client_id: config?.slack_client_id || "", }); const { data: projectIntegration } = useSWR( diff --git a/web/components/issues/attachment/attachment-upload.tsx b/web/components/issues/attachment/attachment-upload.tsx index b5061d8ef..4be4cf11b 100644 --- a/web/components/issues/attachment/attachment-upload.tsx +++ b/web/components/issues/attachment/attachment-upload.tsx @@ -21,7 +21,7 @@ type Props = { export const IssueAttachmentUpload: React.FC = observer((props) => { const { workspaceSlug, disabled = false, handleAttachmentOperations } = props; // store hooks - const { instance } = useInstance(); + const { config } = useInstance(); // states const [isLoading, setIsLoading] = useState(false); @@ -50,12 +50,12 @@ export const IssueAttachmentUpload: React.FC = observer((props) => { const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({ onDrop, - maxSize: instance?.config?.file_size_limit ?? MAX_FILE_SIZE, + maxSize: config?.file_size_limit ?? MAX_FILE_SIZE, multiple: false, disabled: isLoading || disabled, }); - const maxFileSize = instance?.config?.file_size_limit ?? MAX_FILE_SIZE; + const maxFileSize = config?.file_size_limit ?? MAX_FILE_SIZE; const fileError = fileRejections.length > 0 ? `Invalid file type or size (max ${maxFileSize / 1024 / 1024} MB)` : null; diff --git a/web/components/issues/issue-modal/form.tsx b/web/components/issues/issue-modal/form.tsx index 62b18dc15..4c608f83a 100644 --- a/web/components/issues/issue-modal/form.tsx +++ b/web/components/issues/issue-modal/form.tsx @@ -116,7 +116,7 @@ export const IssueFormRoot: FC = observer((props) => { const workspaceStore = useWorkspace(); const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string; const { projectId: routeProjectId } = useAppRouter(); - const { instance } = useInstance(); + const { config } = useInstance(); const { getProjectById } = useProject(); const { areEstimatesEnabledForProject } = useEstimate(); @@ -410,7 +410,7 @@ export const IssueFormRoot: FC = observer((props) => { ) : ( <>
- {issueName && issueName.trim() !== "" && instance?.config?.has_openai_configured && ( + {issueName && issueName.trim() !== "" && config?.has_openai_configured && ( )} - {instance?.config?.has_openai_configured && ( + {config?.has_openai_configured && ( = observer((props) => { // states const [gptModalOpen, setGptModal] = useState(false); // store hooks - const { instance } = useInstance(); + const { config } = useInstance(); // derived values const { archived_at, isContentEditable, isSubmitting, is_locked } = pageStore; @@ -69,7 +69,7 @@ export const PageExtraOptions: React.FC = observer((props) => { Archived at {renderFormattedDate(archived_at)}
)} - {isContentEditable && instance?.config?.has_openai_configured && ( + {isContentEditable && config?.has_openai_configured && ( = observer((props) => { const { children } = props; // store hooks - const { instance } = useInstance(); + const { config } = useInstance(); const { data: currentUser, membership: { currentProjectRole, currentWorkspaceRole }, @@ -53,8 +53,8 @@ export const AppProvider: FC = observer((props) => { currentWorkspaceId={currentWorkspace?.id} workspaceRole={currentWorkspaceRole} projectRole={currentProjectRole} - posthogAPIKey={instance?.config?.posthog_api_key || undefined} - posthogHost={instance?.config?.posthog_host || undefined} + posthogAPIKey={config?.posthog_api_key || undefined} + posthogHost={config?.posthog_host || undefined} > {children} diff --git a/web/lib/wrappers/instance-wrapper.tsx b/web/lib/wrappers/instance-wrapper.tsx index 2945bb9e2..c0343a9e1 100644 --- a/web/lib/wrappers/instance-wrapper.tsx +++ b/web/lib/wrappers/instance-wrapper.tsx @@ -32,7 +32,7 @@ export const InstanceWrapper: FC = observer((props) => { if (error && error?.status === "error") return <>{children}; // instance is not ready and setup is not done - if (instance?.instance?.is_setup_done === false) return ; + if (instance?.is_setup_done === false) return ; return <>{children}; }); diff --git a/web/services/instance.service.ts b/web/services/instance.service.ts index 0e22e58a7..450a7b735 100644 --- a/web/services/instance.service.ts +++ b/web/services/instance.service.ts @@ -1,5 +1,5 @@ // types -import type { IInstance } from "@plane/types"; +import type { IInstanceInfo } from "@plane/types"; // helpers import { API_BASE_URL } from "@/helpers/common.helper"; // services @@ -18,7 +18,7 @@ export class InstanceService extends APIService { }); } - async getInstanceInfo(): Promise { + async getInstanceInfo(): Promise { return this.get("/api/instances/") .then((response) => response.data) .catch((error) => { diff --git a/web/store/instance.store.ts b/web/store/instance.store.ts index aedd96e03..ca813ca17 100644 --- a/web/store/instance.store.ts +++ b/web/store/instance.store.ts @@ -1,6 +1,6 @@ import { observable, action, makeObservable, runInAction } from "mobx"; // types -import { IInstance } from "@plane/types"; +import { IInstance, IInstanceConfig } from "@plane/types"; // services import { InstanceService } from "@/services/instance.service"; @@ -17,6 +17,7 @@ export interface IInstanceStore { // issues isLoading: boolean; instance: IInstance | undefined; + config: IInstanceConfig | undefined; error: TError | undefined; // action fetchInstanceInfo: () => Promise; @@ -25,6 +26,7 @@ export interface IInstanceStore { export class InstanceStore implements IInstanceStore { isLoading: boolean = true; instance: IInstance | undefined = undefined; + config: IInstanceConfig | undefined = undefined; error: TError | undefined = undefined; // services instanceService; @@ -34,6 +36,7 @@ export class InstanceStore implements IInstanceStore { // observable isLoading: observable.ref, instance: observable, + config: observable, error: observable, // actions fetchInstanceInfo: action, @@ -49,10 +52,11 @@ export class InstanceStore implements IInstanceStore { try { this.isLoading = true; this.error = undefined; - const instance = await this.instanceService.getInstanceInfo(); + const instanceInfo = await this.instanceService.getInstanceInfo(); runInAction(() => { this.isLoading = false; - this.instance = instance; + this.instance = instanceInfo.instance; + this.config = instanceInfo.config; }); } catch (error) { runInAction(() => {