fix: instance config errors

This commit is contained in:
sriram veeraghanta 2024-05-18 16:22:53 +05:30
parent 895fbcd5a7
commit 31ca9e447d
35 changed files with 159 additions and 197 deletions

View File

@ -39,8 +39,8 @@ export const AdminLayout: FC<TAdminLayout> = observer((props) => {
<InstanceSidebar /> <InstanceSidebar />
<main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100"> <main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100">
<InstanceHeader /> <InstanceHeader />
</main>
<div className="h-full w-full overflow-hidden">{children}</div> <div className="h-full w-full overflow-hidden">{children}</div>
</main>
<NewUserPopup /> <NewUserPopup />
</div> </div>
); );

View File

@ -1,16 +1,11 @@
import { Metadata } from "next"; import { Metadata } from "next";
// styles
import "@/styles/globals.css";
// components
import { InstanceNotReady, InstanceFailureView } from "@/components/instance";
// helpers // helpers
import { ASSET_PREFIX } from "@/helpers/common.helper"; import { ASSET_PREFIX } from "@/helpers/common.helper";
// lib // components
import { AppProvider } from "@/lib/app-providers"; import { InstanceProvider } from "@/lib/instance-provider";
// services import { StoreProvider } from "@/lib/store-provider";
import { InstanceService } from "@/services/instance.service"; // styles
import "@/styles/globals.css";
const instanceService = new InstanceService();
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Plane Deploy | Make your Plane boards public with one-click", 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 }) { export default function RootLayout({ children }: { children: React.ReactNode }) {
const instanceDetails = await instanceService.getInstanceInfo().catch(() => undefined);
return ( return (
<html lang="en"> <html lang="en">
<head> <head>
@ -40,21 +33,9 @@ export default async function RootLayout({ children }: { children: React.ReactNo
<link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} /> <link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} />
</head> </head>
<body> <body>
<AppProvider initialState={{ instance: instanceDetails }}> <StoreProvider>
{!instanceDetails ? ( <InstanceProvider>{children}</InstanceProvider>
<InstanceFailureView /> </StoreProvider>
) : (
<>
{instanceDetails.instance.is_setup_done ? (
<>{children}</>
) : (
<div className="h-screen w-screen">
<InstanceNotReady />
</div>
)}
</>
)}
</AppProvider>
</body> </body>
</html> </html>
); );

View File

@ -4,7 +4,7 @@ import Image from "next/image";
// assets // assets
import UserLoggedInImage from "public/user-logged-in.svg"; import UserLoggedInImage from "public/user-logged-in.svg";
export default function InstanceNotFound() { export default function NotFound() {
return ( return (
<div className="flex h-screen w-screen flex-col"> <div className="flex h-screen w-screen flex-col">
<div className="grid h-full w-full place-items-center p-6"> <div className="grid h-full w-full place-items-center p-6">

View File

@ -1,7 +1,5 @@
"use client"; "use client";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// components // components
import { UserLoggedIn } from "@/components/account"; import { UserLoggedIn } from "@/components/account";
import { LogoSpinner } from "@/components/common"; import { LogoSpinner } from "@/components/common";
@ -9,15 +7,8 @@ import { AuthView } from "@/components/views";
// hooks // hooks
import { useUser } from "@/hooks/store"; import { useUser } from "@/hooks/store";
function HomePage() { export default function HomePage() {
const { data: currentUser, fetchCurrentUser, isAuthenticated, isLoading } = useUser(); const { data: currentUser, isAuthenticated, isLoading } = useUser();
useSWR("CURRENT_USER", () => fetchCurrentUser(), {
errorRetryCount: 0,
revalidateIfStale: false,
revalidateOnFocus: false,
refreshWhenHidden: false,
});
if (isLoading) return <LogoSpinner />; if (isLoading) return <LogoSpinner />;
@ -25,5 +16,3 @@ function HomePage() {
return <AuthView />; return <AuthView />;
} }
export default observer(HomePage);

View File

@ -43,7 +43,7 @@ export const AuthRoot: FC = observer(() => {
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined); const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
const [isPasswordAutoset, setIsPasswordAutoset] = useState(true); const [isPasswordAutoset, setIsPasswordAutoset] = useState(true);
// hooks // hooks
const { instance } = useInstance(); const { config } = useInstance();
useEffect(() => { useEffect(() => {
if (error_code) { if (error_code) {
@ -67,11 +67,10 @@ export const AuthRoot: FC = observer(() => {
} }
}, [error_code]); }, [error_code]);
const isSMTPConfigured = instance?.config?.is_smtp_configured || false; const isSMTPConfigured = config?.is_smtp_configured || false;
const isMagicLoginEnabled = instance?.config?.is_magic_login_enabled || false; const isMagicLoginEnabled = config?.is_magic_login_enabled || false;
const isEmailPasswordEnabled = instance?.config?.is_email_password_enabled || false; const isEmailPasswordEnabled = config?.is_email_password_enabled || false;
const isOAuthEnabled = const isOAuthEnabled = (config && (config?.is_google_enabled || config?.is_github_enabled)) || false;
(instance?.config && (instance?.config?.is_google_enabled || instance?.config?.is_github_enabled)) || false;
// submit handler- email verification // submit handler- email verification
const handleEmailVerification = async (data: IEmailCheckData) => { const handleEmailVerification = async (data: IEmailCheckData) => {

View File

@ -6,7 +6,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 } = useInstance();
return ( return (
<> <>
@ -16,12 +16,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={`mt-7 grid gap-4 overflow-hidden`}> <div className={`mt-7 grid gap-4 overflow-hidden`}>
{instance?.config?.is_google_enabled && ( {config?.is_google_enabled && (
<div className="flex h-[42px] items-center !overflow-hidden"> <div className="flex h-[42px] items-center !overflow-hidden">
<GoogleOAuthButton text="Sign in with Google" /> <GoogleOAuthButton text="Sign in with Google" />
</div> </div>
)} )}
{instance?.config?.is_github_enabled && <GithubOAuthButton text="Sign in with Github" />} {config?.is_github_enabled && <GithubOAuthButton text="Sign in with Github" />}
</div> </div>
</> </>
); );

View File

@ -1,6 +1,6 @@
import { useContext } from "react"; import { useContext } from "react";
// lib // lib
import { StoreContext } from "@/lib/app-providers"; import { StoreContext } from "@/lib/store-provider";
// store // store
import { IInstanceStore } from "@/store/instance.store"; import { IInstanceStore } from "@/store/instance.store";

View File

@ -1,6 +1,6 @@
import { useContext } from "react"; import { useContext } from "react";
// lib // lib
import { StoreContext } from "@/lib/app-providers"; import { StoreContext } from "@/lib/store-provider";
// store // store
import { IIssueDetailStore } from "@/store/issue-detail.store"; import { IIssueDetailStore } from "@/store/issue-detail.store";

View File

@ -1,6 +1,6 @@
import { useContext } from "react"; import { useContext } from "react";
// lib // lib
import { StoreContext } from "@/lib/app-providers"; import { StoreContext } from "@/lib/store-provider";
// store // store
import { IIssueFilterStore } from "@/store/issue-filters.store"; import { IIssueFilterStore } from "@/store/issue-filters.store";

View File

@ -1,6 +1,6 @@
import { useContext } from "react"; import { useContext } from "react";
// lib // lib
import { StoreContext } from "@/lib/app-providers"; import { StoreContext } from "@/lib/store-provider";
// store // store
import { IIssueStore } from "@/store/issue.store"; import { IIssueStore } from "@/store/issue.store";

View File

@ -1,6 +1,6 @@
import { useContext } from "react"; import { useContext } from "react";
// lib // lib
import { StoreContext } from "@/lib/app-providers"; import { StoreContext } from "@/lib/store-provider";
// store // store
import { IProjectStore } from "@/store/project.store"; import { IProjectStore } from "@/store/project.store";

View File

@ -1,6 +1,6 @@
import { useContext } from "react"; import { useContext } from "react";
// lib // lib
import { StoreContext } from "@/lib/app-providers"; import { StoreContext } from "@/lib/store-provider";
// store // store
import { IProfileStore } from "@/store/profile.store"; import { IProfileStore } from "@/store/profile.store";

View File

@ -1,6 +1,6 @@
import { useContext } from "react"; import { useContext } from "react";
// lib // lib
import { StoreContext } from "@/lib/app-providers"; import { StoreContext } from "@/lib/store-provider";
// store // store
import { IUserStore } from "@/store/user.store"; import { IUserStore } from "@/store/user.store";

View File

@ -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 (
<div className="flex h-screen min-h-[500px] w-full justify-center items-center">
<LogoSpinner />
</div>
);
if (error) {
return (
<div className="relative">
<div className="h-screen w-full overflow-hidden overflow-y-auto flex flex-col">
<div className="container h-[110px] flex-shrink-0 mx-auto px-5 lg:px-0 flex items-center justify-between gap-5 z-50">
<div className="flex items-center gap-x-2 py-10">
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" />
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
</div>
</div>
<div className="absolute inset-0 z-0">
<Image src={patternBackground} className="w-screen h-full object-cover" alt="Plane background pattern" />
</div>
<div className="relative z-10 flex-grow">
<div className="relative h-full w-full overflow-y-auto px-6 py-10 mx-auto flex justify-center items-center">
<InstanceFailureView />
</div>
</div>
</div>
</div>
);
}
return <>{children}</>;
});

View File

@ -23,12 +23,13 @@ function initializeStore(initialData = {}) {
return singletonRootStore; return singletonRootStore;
} }
export type AppProviderProps = { export type StoreProviderProps = {
children: ReactNode; 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); const store = initializeStore(initialState);
return ( return (
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem> <ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>

View File

@ -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}</>;
});

View File

@ -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<TAuthWrapper> = 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 (
<div className="relative flex h-screen w-full items-center justify-center">
<Spinner />
</div>
);
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}</>;
});

View File

@ -1 +0,0 @@
export * from "./auth-wrapper";

View File

@ -1,5 +1,5 @@
// types // types
import type { IInstance } from "@plane/types"; import type { IInstanceInfo } from "@plane/types";
// helpers // helpers
import { API_BASE_URL } from "@/helpers/common.helper"; import { API_BASE_URL } from "@/helpers/common.helper";
// services // services
@ -10,7 +10,7 @@ export class InstanceService extends APIService {
super(API_BASE_URL); super(API_BASE_URL);
} }
async getInstanceInfo(): Promise<IInstance> { async getInstanceInfo(): Promise<IInstanceInfo> {
return this.get("/api/instances/") return this.get("/api/instances/")
.then((response) => response.data) .then((response) => response.data)
.catch((error) => { .catch((error) => {

View File

@ -1,7 +1,7 @@
import set from "lodash/set"; import set from "lodash/set";
import { observable, action, makeObservable, runInAction } from "mobx"; import { observable, action, makeObservable, runInAction } from "mobx";
// types // types
import { IInstance } from "@plane/types"; import { IInstance, IInstanceConfig } from "@plane/types";
// services // services
import { InstanceService } from "@/services/instance.service"; import { InstanceService } from "@/services/instance.service";
// store types // store types
@ -20,6 +20,7 @@ export interface IInstanceStore {
// observables // observables
isLoading: boolean; isLoading: boolean;
instance: IInstance | undefined; instance: IInstance | undefined;
config: IInstanceConfig | undefined;
error: TError | undefined; error: TError | undefined;
// action // action
fetchInstanceInfo: () => Promise<void>; fetchInstanceInfo: () => Promise<void>;
@ -29,6 +30,7 @@ export interface IInstanceStore {
export class InstanceStore implements IInstanceStore { export class InstanceStore implements IInstanceStore {
isLoading: boolean = true; isLoading: boolean = true;
instance: IInstance | undefined = undefined; instance: IInstance | undefined = undefined;
config: IInstanceConfig | undefined = undefined;
error: TError | undefined = undefined; error: TError | undefined = undefined;
// services // services
instanceService; instanceService;
@ -38,6 +40,7 @@ export class InstanceStore implements IInstanceStore {
// observable // observable
isLoading: observable.ref, isLoading: observable.ref,
instance: observable, instance: observable,
config: observable,
error: observable, error: observable,
// actions // actions
fetchInstanceInfo: action, fetchInstanceInfo: action,
@ -56,10 +59,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 instanceInfo = await this.instanceService.getInstanceInfo();
runInAction(() => { runInAction(() => {
this.isLoading = false; this.isLoading = false;
this.instance = instance; this.instance = instanceInfo.instance;
this.config = instanceInfo.config;
}); });
} catch (error) { } catch (error) {
runInAction(() => { runInAction(() => {

View File

@ -44,7 +44,7 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined); const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
const [isPasswordAutoset, setIsPasswordAutoset] = useState(true); const [isPasswordAutoset, setIsPasswordAutoset] = useState(true);
// hooks // hooks
const { instance } = useInstance(); const { config } = useInstance();
useEffect(() => { useEffect(() => {
if (error_code) { if (error_code) {
@ -68,9 +68,9 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
} }
}, [error_code, authMode]); }, [error_code, authMode]);
const isSMTPConfigured = instance?.config?.is_smtp_configured || false; const isSMTPConfigured = config?.is_smtp_configured || false;
const isMagicLoginEnabled = instance?.config?.is_magic_login_enabled || false; const isMagicLoginEnabled = config?.is_magic_login_enabled || false;
const isEmailPasswordEnabled = instance?.config?.is_email_password_enabled || false; const isEmailPasswordEnabled = config?.is_email_password_enabled || false;
// submit handler- email verification // submit handler- email verification
const handleEmailVerification = async (data: IEmailCheckData) => { const handleEmailVerification = async (data: IEmailCheckData) => {

View File

@ -11,10 +11,9 @@ type TOAuthOptionProps = {
export const OAuthOptions: React.FC<TOAuthOptionProps> = observer((props) => { export const OAuthOptions: React.FC<TOAuthOptionProps> = observer((props) => {
const { isSignUp = false } = props; const { isSignUp = false } = props;
// hooks // hooks
const { instance } = useInstance(); const { config } = useInstance();
const isOAuthEnabled = const isOAuthEnabled = (config && (config?.is_google_enabled || config?.is_github_enabled)) || false;
(instance?.config && (instance?.config?.is_google_enabled || instance?.config?.is_github_enabled)) || false;
if (!isOAuthEnabled) return null; if (!isOAuthEnabled) return null;
@ -28,12 +27,12 @@ export const OAuthOptions: React.FC<TOAuthOptionProps> = observer((props) => {
<hr className="w-full border-onboarding-border-100" /> <hr className="w-full border-onboarding-border-100" />
</div> </div>
<div className={`mt-7 grid gap-4 overflow-hidden`}> <div className={`mt-7 grid gap-4 overflow-hidden`}>
{instance?.config?.is_google_enabled && ( {config?.is_google_enabled && (
<div className="flex h-[42px] items-center !overflow-hidden"> <div className="flex h-[42px] items-center !overflow-hidden">
<GoogleOAuthButton text={`${oauthProviderButtonText} Google`} /> <GoogleOAuthButton text={`${oauthProviderButtonText} Google`} />
</div> </div>
)} )}
{instance?.config?.is_github_enabled && <GithubOAuthButton text={`${oauthProviderButtonText} Github`} />} {config?.is_github_enabled && <GithubOAuthButton text={`${oauthProviderButtonText} Github`} />}
</div> </div>
</> </>
); );

View File

@ -62,7 +62,7 @@ export const ImagePickerPopover: React.FC<Props> = observer((props) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
// store hooks // store hooks
const { instance } = useInstance(); const { config } = useInstance();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { data: unsplashImages, error: unsplashError } = useSWR( const { data: unsplashImages, error: unsplashError } = useSWR(
@ -90,7 +90,7 @@ export const ImagePickerPopover: 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: config?.file_size_limit ?? MAX_FILE_SIZE,
}); });
const handleSubmit = async () => { const handleSubmit = async () => {

View File

@ -30,7 +30,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 } = useInstance();
const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]); const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]);
@ -39,7 +39,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: config?.file_size_limit ?? MAX_FILE_SIZE,
multiple: false, multiple: false,
}); });

View File

@ -34,7 +34,7 @@ export const WorkspaceImageUploadModal: React.FC<Props> = observer((props) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
// store hooks // store hooks
const { instance } = useInstance(); const { config } = useInstance();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]); const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]);
@ -44,7 +44,7 @@ export const WorkspaceImageUploadModal: 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: config?.file_size_limit ?? MAX_FILE_SIZE,
multiple: false, multiple: false,
}); });

View File

@ -14,12 +14,12 @@ type Props = {
export const GithubAuth: React.FC<Props> = observer(({ workspaceIntegration, provider }) => { export const GithubAuth: React.FC<Props> = observer(({ workspaceIntegration, provider }) => {
// store hooks // store hooks
const { instance } = useInstance(); const { config } = useInstance();
// hooks // hooks
const { startAuth, isConnecting } = useIntegrationPopup({ const { startAuth, isConnecting } = useIntegrationPopup({
provider, provider,
github_app_name: instance?.config?.github_app_name || "", github_app_name: config?.github_app_name || "",
slack_client_id: instance?.config?.slack_client_id || "", slack_client_id: config?.slack_client_id || "",
}); });
return ( return (

View File

@ -46,7 +46,7 @@ export const SingleIntegrationCard: React.FC<Props> = observer(({ integration })
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
// store hooks // store hooks
const { instance } = useInstance(); const { config } = useInstance();
const { const {
membership: { currentWorkspaceRole }, membership: { currentWorkspaceRole },
} = useUser(); } = useUser();
@ -55,8 +55,8 @@ export const SingleIntegrationCard: React.FC<Props> = observer(({ integration })
const { isMobile } = usePlatformOS(); const { isMobile } = usePlatformOS();
const { startAuth, isConnecting: isInstalling } = useIntegrationPopup({ const { startAuth, isConnecting: isInstalling } = useIntegrationPopup({
provider: integration.provider, provider: integration.provider,
github_app_name: instance?.config?.github_app_name || "", github_app_name: config?.github_app_name || "",
slack_client_id: instance?.config?.slack_client_id || "", slack_client_id: config?.slack_client_id || "",
}); });
const { data: workspaceIntegrations } = useSWR( const { data: workspaceIntegrations } = useSWR(

View File

@ -22,7 +22,7 @@ const appInstallationService = new AppInstallationService();
export const SelectChannel: React.FC<Props> = observer(({ integration }) => { export const SelectChannel: React.FC<Props> = observer(({ integration }) => {
// store hooks // store hooks
const { instance } = useInstance(); const { config } = useInstance();
// states // states
const [slackChannelAvailabilityToggle, setSlackChannelAvailabilityToggle] = useState<boolean>(false); const [slackChannelAvailabilityToggle, setSlackChannelAvailabilityToggle] = useState<boolean>(false);
const [slackChannel, setSlackChannel] = useState<ISlackIntegration | null>(null); const [slackChannel, setSlackChannel] = useState<ISlackIntegration | null>(null);
@ -35,7 +35,7 @@ export const SelectChannel: React.FC<Props> = observer(({ integration }) => {
provider: "slackChannel", provider: "slackChannel",
stateParams: integration.id, stateParams: integration.id,
// github_app_name: instance?.config?.github_client_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( const { data: projectIntegration } = useSWR(

View File

@ -21,7 +21,7 @@ type Props = {
export const IssueAttachmentUpload: React.FC<Props> = observer((props) => { export const IssueAttachmentUpload: React.FC<Props> = observer((props) => {
const { workspaceSlug, disabled = false, handleAttachmentOperations } = props; const { workspaceSlug, disabled = false, handleAttachmentOperations } = props;
// store hooks // store hooks
const { instance } = useInstance(); const { config } = useInstance();
// states // states
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -50,12 +50,12 @@ export const IssueAttachmentUpload: React.FC<Props> = observer((props) => {
const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({ const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({
onDrop, onDrop,
maxSize: instance?.config?.file_size_limit ?? MAX_FILE_SIZE, maxSize: config?.file_size_limit ?? MAX_FILE_SIZE,
multiple: false, multiple: false,
disabled: isLoading || disabled, disabled: isLoading || disabled,
}); });
const maxFileSize = instance?.config?.file_size_limit ?? MAX_FILE_SIZE; const maxFileSize = config?.file_size_limit ?? MAX_FILE_SIZE;
const fileError = const fileError =
fileRejections.length > 0 ? `Invalid file type or size (max ${maxFileSize / 1024 / 1024} MB)` : null; fileRejections.length > 0 ? `Invalid file type or size (max ${maxFileSize / 1024 / 1024} MB)` : null;

View File

@ -116,7 +116,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
const workspaceStore = useWorkspace(); const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string; const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
const { projectId: routeProjectId } = useAppRouter(); const { projectId: routeProjectId } = useAppRouter();
const { instance } = useInstance(); const { config } = useInstance();
const { getProjectById } = useProject(); const { getProjectById } = useProject();
const { areEstimatesEnabledForProject } = useEstimate(); const { areEstimatesEnabledForProject } = useEstimate();
@ -410,7 +410,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
) : ( ) : (
<> <>
<div className="border-0.5 absolute bottom-3.5 right-3.5 z-10 flex items-center gap-2"> <div className="border-0.5 absolute bottom-3.5 right-3.5 z-10 flex items-center gap-2">
{issueName && issueName.trim() !== "" && instance?.config?.has_openai_configured && ( {issueName && issueName.trim() !== "" && config?.has_openai_configured && (
<button <button
type="button" type="button"
className={`flex items-center gap-1 rounded bg-custom-background-90 px-1.5 py-1 text-xs ${ className={`flex items-center gap-1 rounded bg-custom-background-90 px-1.5 py-1 text-xs ${
@ -429,7 +429,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
)} )}
</button> </button>
)} )}
{instance?.config?.has_openai_configured && ( {config?.has_openai_configured && (
<GptAssistantPopover <GptAssistantPopover
isOpen={gptAssistantModal} isOpen={gptAssistantModal}
projectId={projectId} projectId={projectId}

View File

@ -30,7 +30,7 @@ export const PageExtraOptions: React.FC<Props> = observer((props) => {
// states // states
const [gptModalOpen, setGptModal] = useState(false); const [gptModalOpen, setGptModal] = useState(false);
// store hooks // store hooks
const { instance } = useInstance(); const { config } = useInstance();
// derived values // derived values
const { archived_at, isContentEditable, isSubmitting, is_locked } = pageStore; const { archived_at, isContentEditable, isSubmitting, is_locked } = pageStore;
@ -69,7 +69,7 @@ export const PageExtraOptions: React.FC<Props> = observer((props) => {
<span>Archived at {renderFormattedDate(archived_at)}</span> <span>Archived at {renderFormattedDate(archived_at)}</span>
</div> </div>
)} )}
{isContentEditable && instance?.config?.has_openai_configured && ( {isContentEditable && config?.has_openai_configured && (
<GptAssistantPopover <GptAssistantPopover
isOpen={gptModalOpen} isOpen={gptModalOpen}
projectId={projectId} projectId={projectId}

View File

@ -32,7 +32,7 @@ export interface IAppProvider {
export const AppProvider: FC<IAppProvider> = observer((props) => { export const AppProvider: FC<IAppProvider> = observer((props) => {
const { children } = props; const { children } = props;
// store hooks // store hooks
const { instance } = useInstance(); const { config } = useInstance();
const { const {
data: currentUser, data: currentUser,
membership: { currentProjectRole, currentWorkspaceRole }, membership: { currentProjectRole, currentWorkspaceRole },
@ -53,8 +53,8 @@ export const AppProvider: FC<IAppProvider> = observer((props) => {
currentWorkspaceId={currentWorkspace?.id} currentWorkspaceId={currentWorkspace?.id}
workspaceRole={currentWorkspaceRole} workspaceRole={currentWorkspaceRole}
projectRole={currentProjectRole} projectRole={currentProjectRole}
posthogAPIKey={instance?.config?.posthog_api_key || undefined} posthogAPIKey={config?.posthog_api_key || undefined}
posthogHost={instance?.config?.posthog_host || undefined} posthogHost={config?.posthog_host || undefined}
> >
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig> <SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
</PostHogProvider> </PostHogProvider>

View File

@ -32,7 +32,7 @@ export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
if (error && error?.status === "error") return <>{children}</>; if (error && error?.status === "error") return <>{children}</>;
// instance is not ready and setup is not done // instance is not ready and setup is not done
if (instance?.instance?.is_setup_done === false) return <InstanceNotReady />; if (instance?.is_setup_done === false) return <InstanceNotReady />;
return <>{children}</>; return <>{children}</>;
}); });

View File

@ -1,5 +1,5 @@
// types // types
import type { IInstance } from "@plane/types"; import type { IInstanceInfo } from "@plane/types";
// helpers // helpers
import { API_BASE_URL } from "@/helpers/common.helper"; import { API_BASE_URL } from "@/helpers/common.helper";
// services // services
@ -18,7 +18,7 @@ export class InstanceService extends APIService {
}); });
} }
async getInstanceInfo(): Promise<IInstance> { async getInstanceInfo(): Promise<IInstanceInfo> {
return this.get("/api/instances/") return this.get("/api/instances/")
.then((response) => response.data) .then((response) => response.data)
.catch((error) => { .catch((error) => {

View File

@ -1,6 +1,6 @@
import { observable, action, makeObservable, runInAction } from "mobx"; import { observable, action, makeObservable, runInAction } from "mobx";
// types // types
import { IInstance } from "@plane/types"; import { IInstance, IInstanceConfig } from "@plane/types";
// services // services
import { InstanceService } from "@/services/instance.service"; import { InstanceService } from "@/services/instance.service";
@ -17,6 +17,7 @@ export interface IInstanceStore {
// issues // issues
isLoading: boolean; isLoading: boolean;
instance: IInstance | undefined; instance: IInstance | undefined;
config: IInstanceConfig | undefined;
error: TError | undefined; error: TError | undefined;
// action // action
fetchInstanceInfo: () => Promise<void>; fetchInstanceInfo: () => Promise<void>;
@ -25,6 +26,7 @@ export interface IInstanceStore {
export class InstanceStore implements IInstanceStore { export class InstanceStore implements IInstanceStore {
isLoading: boolean = true; isLoading: boolean = true;
instance: IInstance | undefined = undefined; instance: IInstance | undefined = undefined;
config: IInstanceConfig | undefined = undefined;
error: TError | undefined = undefined; error: TError | undefined = undefined;
// services // services
instanceService; instanceService;
@ -34,6 +36,7 @@ export class InstanceStore implements IInstanceStore {
// observable // observable
isLoading: observable.ref, isLoading: observable.ref,
instance: observable, instance: observable,
config: observable,
error: observable, error: observable,
// actions // actions
fetchInstanceInfo: action, fetchInstanceInfo: action,
@ -49,10 +52,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 instanceInfo = await this.instanceService.getInstanceInfo();
runInAction(() => { runInAction(() => {
this.isLoading = false; this.isLoading = false;
this.instance = instance; this.instance = instanceInfo.instance;
this.config = instanceInfo.config;
}); });
} catch (error) { } catch (error) {
runInAction(() => { runInAction(() => {