mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'fix/instance-layouts' of github.com:makeplane/plane into preview
This commit is contained in:
commit
87610399c4
@ -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 <AdminLayout>{children}</AdminLayout>;
|
||||
}
|
||||
|
@ -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 <AdminLayout>{children}</AdminLayout>;
|
||||
}
|
||||
|
@ -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) => <AdminLayout>{children}</AdminLayout>;
|
||||
|
||||
export default EmailLayout;
|
||||
|
@ -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<IGeneralConfigurationForm> = observer(
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<Partial<IInstance["instance"]>>({
|
||||
} = useForm<Partial<IInstance>>({
|
||||
defaultValues: {
|
||||
instance_name: instance?.instance_name,
|
||||
is_telemetry_enabled: instance?.is_telemetry_enabled,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: Partial<IInstance["instance"]>) => {
|
||||
const payload: Partial<IInstance["instance"]> = { ...formData };
|
||||
const onSubmit = async (formData: Partial<IInstance>) => {
|
||||
const payload: Partial<IInstance> = { ...formData };
|
||||
|
||||
console.log("payload", payload);
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ReactNode } from "react";
|
||||
import { Metadata } from "next";
|
||||
// components
|
||||
import { AdminLayout } from "@/layouts/admin-layout";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
|
@ -7,7 +7,7 @@ import { GeneralConfigurationForm } from "./form";
|
||||
|
||||
function GeneralPage() {
|
||||
const { instance, instanceAdmins } = useInstance();
|
||||
console.log("instance", instanceAdmins);
|
||||
console.log("instance", instance);
|
||||
return (
|
||||
<>
|
||||
<div className="relative container mx-auto w-full h-full p-8 py-4 space-y-6 flex flex-col">
|
||||
@ -19,8 +19,8 @@ function GeneralPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-grow overflow-hidden overflow-y-auto">
|
||||
{instance?.instance && instanceAdmins && (
|
||||
<GeneralConfigurationForm instance={instance.instance} instanceAdmins={instanceAdmins} />
|
||||
{instance && instanceAdmins && (
|
||||
<GeneralConfigurationForm instance={instance} instanceAdmins={instanceAdmins} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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) => <AdminLayout>{children}</AdminLayout>;
|
||||
|
||||
export default ImageLayout;
|
||||
|
@ -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 (
|
||||
<html lang="en">
|
||||
<head>
|
||||
@ -45,28 +25,18 @@ export default async function RootLayout({ children }: { children: ReactNode })
|
||||
<link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} />
|
||||
</head>
|
||||
<body className={`antialiased`}>
|
||||
<AppProvider initialState={{ instance: instanceDetails }}>
|
||||
{instanceDetails ? (
|
||||
<>
|
||||
{instanceDetails?.instance?.is_setup_done ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
<DefaultLayout>
|
||||
<div className="relative w-screen min-h-screen overflow-y-auto px-5 py-10 mx-auto flex justify-center items-center">
|
||||
<InstanceSetupForm />
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<DefaultLayout>
|
||||
<div className="relative w-screen min-h-[500px] overflow-y-auto px-5 mx-auto flex justify-center items-center">
|
||||
<InstanceFailureView />
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
)}
|
||||
</AppProvider>
|
||||
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
||||
<SWRConfig value={SWR_CONFIG}>
|
||||
<StoreProvider>
|
||||
<InstanceProvider>
|
||||
<UserProvider>{children}</UserProvider>
|
||||
</InstanceProvider>
|
||||
</StoreProvider>
|
||||
</SWRConfig>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
export default RootLayout;
|
||||
|
@ -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 (
|
||||
<DefaultLayout>
|
||||
|
@ -6,3 +6,4 @@ export * from "./password-strength-meter";
|
||||
export * from "./banner";
|
||||
export * from "./empty-state";
|
||||
export * from "./logo-spinner";
|
||||
export * from "./toast";
|
||||
|
@ -11,7 +11,7 @@ export const LogoSpinner = () => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center">
|
||||
<Image src={logoSrc} alt="logo" className="w-[82px] h-[82px] mr-2" />
|
||||
<Image src={logoSrc} alt="logo" className="w-[82px] h-[82px] mr-2" priority={false} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -43,7 +43,7 @@ export const PasswordStrengthMeter: React.FC<Props> = (props: Props) => {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="w-full p-1">
|
||||
<div className="w-full">
|
||||
<div className="flex w-full gap-1.5">
|
||||
{bars.map((color, index) => (
|
||||
<div key={index} className={cn("w-full h-1 rounded-full", color)} />
|
||||
|
11
admin/components/common/toast.tsx
Normal file
11
admin/components/common/toast.tsx
Normal file
@ -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 <ToastComponent theme={resolveGeneralTheme(theme)} />;
|
||||
};
|
@ -21,7 +21,7 @@ export const InstanceFailureView: FC<InstanceFailureViewProps> = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center mt-10">
|
||||
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center">
|
||||
<div className="w-auto max-w-2xl relative space-y-8 py-10">
|
||||
<div className="relative flex flex-col justify-center items-center space-y-4">
|
||||
<Image src={instanceImage} alt="Plane Logo" />
|
||||
|
@ -129,7 +129,7 @@ export const InstanceSetupForm: FC = (props) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="max-w-lg px-10 lg:max-w-md lg:px-5">
|
||||
<div className="max-w-lg lg:max-w-md w-full">
|
||||
<div className="relative flex flex-col space-y-6">
|
||||
<div className="text-center space-y-1">
|
||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
||||
@ -155,7 +155,7 @@ export const InstanceSetupForm: FC = (props) => {
|
||||
>
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex flex-col sm:flex-row items-center gap-4">
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="first_name">
|
||||
First name <span className="text-red-500">*</span>
|
||||
|
@ -17,11 +17,11 @@ import TakeoffIconDark from "/public/logos/takeoff-icon-dark.svg";
|
||||
export const NewUserPopup: React.FC = observer(() => {
|
||||
// hooks
|
||||
const { isNewUserPopup, toggleNewUserPopup } = useTheme();
|
||||
const { instance } = useInstance();
|
||||
const { config } = useInstance();
|
||||
// theme
|
||||
const { resolvedTheme } = nextUseTheme();
|
||||
|
||||
const redirectionLink = `${instance?.config?.app_base_url ? `${instance?.config?.app_base_url}/create-workspace` : `/god-mode/`}`;
|
||||
const redirectionLink = `${config?.app_base_url ? `${config?.app_base_url}/create-workspace` : `/god-mode/`}`;
|
||||
|
||||
if (!isNewUserPopup) return <></>;
|
||||
return (
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useContext } from "react";
|
||||
// store
|
||||
import { StoreContext } from "@/lib/app-providers";
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
import { IInstanceStore } from "@/store/instance.store";
|
||||
|
||||
export const useInstance = (): IInstanceStore => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useContext } from "react";
|
||||
// store
|
||||
import { StoreContext } from "@/lib/app-providers";
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
import { IThemeStore } from "@/store/theme.store";
|
||||
|
||||
export const useTheme = (): IThemeStore => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useContext } from "react";
|
||||
// store
|
||||
import { StoreContext } from "@/lib/app-providers";
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
import { IUserStore } from "@/store/user.store";
|
||||
|
||||
export const useUser = (): IUserStore => {
|
||||
|
@ -2,14 +2,13 @@
|
||||
import { FC, ReactNode, useEffect } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { InstanceSidebar } from "@/components/admin-sidebar";
|
||||
import { InstanceHeader } from "@/components/auth-header";
|
||||
import { LogoSpinner } from "@/components/common";
|
||||
import { NewUserPopup } from "@/components/new-user-popup";
|
||||
// hooks
|
||||
import { useInstance, useUser } from "@/hooks/store";
|
||||
import { useUser } from "@/hooks/store";
|
||||
|
||||
type TAdminLayout = {
|
||||
children: ReactNode;
|
||||
@ -19,15 +18,7 @@ export const AdminLayout: FC<TAdminLayout> = observer((props) => {
|
||||
const { children } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
// hooks
|
||||
const { fetchInstanceAdmins } = useInstance();
|
||||
const { fetchCurrentUser, isUserLoggedIn } = useUser();
|
||||
|
||||
useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins());
|
||||
|
||||
useSWR("CURRENT_USER", () => fetchCurrentUser(), {
|
||||
shouldRetryOnError: false,
|
||||
});
|
||||
const { isUserLoggedIn } = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
if (isUserLoggedIn === false) {
|
||||
|
@ -33,7 +33,7 @@ export const DefaultLayout: FC<TDefaultLayout> = (props) => {
|
||||
<Image src={patternBackground} className="w-screen h-full object-cover" alt="Plane background pattern" />
|
||||
</div>
|
||||
)}
|
||||
<div className="relative z-10 mb-[110px] flex-grow">{children}</div>
|
||||
<div className="relative z-10 flex-grow">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -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<IAppWrapper> = 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 (
|
||||
<Suspense>
|
||||
<Toast theme={resolveGeneralTheme(theme)} />
|
||||
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
|
||||
</Suspense>
|
||||
);
|
||||
});
|
55
admin/lib/instance-provider.tsx
Normal file
55
admin/lib/instance-provider.tsx
Normal file
@ -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<InstanceProviderProps> = 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 (
|
||||
<div className="flex h-screen min-h-[500px] w-full justify-center items-center">
|
||||
<LogoSpinner />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<div className="relative h-full w-full overflow-y-auto px-6 py-10 mx-auto flex justify-center items-center">
|
||||
<InstanceFailureView />
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
);
|
||||
}
|
||||
|
||||
if (!instance?.is_setup_done) {
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<div className="relative h-full w-full overflow-y-auto px-6 py-10 mx-auto flex justify-center items-center">
|
||||
<InstanceSetupForm />
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
});
|
@ -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 (
|
||||
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
||||
<StoreContext.Provider value={store}>
|
||||
<AppWrapper>{children}</AppWrapper>
|
||||
</StoreContext.Provider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
|
||||
};
|
31
admin/lib/user-provider.tsx
Normal file
31
admin/lib/user-provider.tsx
Normal file
@ -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<IUserProvider> = 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}</>;
|
||||
});
|
@ -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<IInstance> {
|
||||
return this.get<IInstance>("/api/instances/")
|
||||
async getInstanceInfo(): Promise<IInstanceInfo> {
|
||||
return this.get<IInstanceInfo>("/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<IInstance["instance"]>): Promise<IInstance["instance"]> {
|
||||
return this.patch<Partial<IInstance["instance"]>, IInstance["instance"]>("/api/instances/", data)
|
||||
async updateInstanceInfo(data: Partial<IInstance>): Promise<IInstance> {
|
||||
return this.patch<Partial<IInstance>, IInstance>("/api/instances/", data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
|
@ -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<IInstance | undefined>;
|
||||
updateInstanceInfo: (data: Partial<IInstance["instance"]>) => Promise<IInstance["instance"] | undefined>;
|
||||
hydrate: (data: IInstanceInfo) => void;
|
||||
fetchInstanceInfo: () => Promise<IInstanceInfo | undefined>;
|
||||
updateInstanceInfo: (data: Partial<IInstance>) => Promise<IInstance | undefined>;
|
||||
fetchInstanceAdmins: () => Promise<IInstanceAdmin[] | undefined>;
|
||||
fetchInstanceConfigurations: () => Promise<IInstanceConfiguration[] | undefined>;
|
||||
updateInstanceConfigurations: (data: Partial<IFormattedInstanceConfiguration>) => Promise<IInstanceConfiguration[]>;
|
||||
@ -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<IInstance["instance"]>} data
|
||||
* @param {Partial<IInstance>} data
|
||||
* @returns void
|
||||
*/
|
||||
updateInstanceInfo = async (data: Partial<IInstance["instance"]>) => {
|
||||
updateInstanceInfo = async (data: Partial<IInstance>) => {
|
||||
try {
|
||||
const instanceResponse = await this.instanceService.updateInstanceInfo(data);
|
||||
if (instanceResponse) {
|
||||
|
84
packages/types/src/instance/base.d.ts
vendored
84
packages/types/src/instance/base.d.ts
vendored
@ -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 {
|
||||
|
@ -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 (
|
||||
<html lang="en">
|
||||
<head>
|
||||
@ -40,21 +33,9 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
<link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} />
|
||||
</head>
|
||||
<body>
|
||||
<AppProvider initialState={{ instance: instanceDetails }}>
|
||||
{!instanceDetails ? (
|
||||
<InstanceFailureView />
|
||||
) : (
|
||||
<>
|
||||
{instanceDetails.instance.is_setup_done ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
<div className="h-screen w-screen">
|
||||
<InstanceNotReady />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</AppProvider>
|
||||
<StoreProvider>
|
||||
<InstanceProvider>{children}</InstanceProvider>
|
||||
</StoreProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
@ -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 (
|
||||
<div className="flex h-screen w-screen flex-col">
|
||||
<div className="grid h-full w-full place-items-center p-6">
|
||||
|
@ -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 <LogoSpinner />;
|
||||
|
||||
@ -25,5 +16,3 @@ function HomePage() {
|
||||
|
||||
return <AuthView />;
|
||||
}
|
||||
|
||||
export default observer(HomePage);
|
||||
|
@ -43,7 +43,7 @@ export const AuthRoot: FC = observer(() => {
|
||||
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(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) => {
|
||||
|
@ -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(() => {
|
||||
<hr className="w-full border-onboarding-border-100" />
|
||||
</div>
|
||||
<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">
|
||||
<GoogleOAuthButton text="Sign in with Google" />
|
||||
</div>
|
||||
)}
|
||||
{instance?.config?.is_github_enabled && <GithubOAuthButton text="Sign in with Github" />}
|
||||
{config?.is_github_enabled && <GithubOAuthButton text="Sign in with Github" />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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";
|
||||
|
||||
|
63
space/lib/instance-provider.tsx
Normal file
63
space/lib/instance-provider.tsx
Normal 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}</>;
|
||||
});
|
@ -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 (
|
||||
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
12
space/lib/user-provider.tsx
Normal file
12
space/lib/user-provider.tsx
Normal 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}</>;
|
||||
});
|
@ -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}</>;
|
||||
});
|
@ -1 +0,0 @@
|
||||
export * from "./auth-wrapper";
|
@ -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<IInstance> {
|
||||
async getInstanceInfo(): Promise<IInstanceInfo> {
|
||||
return this.get("/api/instances/")
|
||||
.then((response) => response.data)
|
||||
.catch((error) => {
|
||||
|
@ -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<void>;
|
||||
@ -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(() => {
|
||||
|
@ -44,7 +44,7 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
|
||||
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(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<TAuthRoot> = 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) => {
|
||||
|
@ -11,10 +11,9 @@ type TOAuthOptionProps = {
|
||||
export const OAuthOptions: React.FC<TOAuthOptionProps> = 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<TOAuthOptionProps> = observer((props) => {
|
||||
<hr className="w-full border-onboarding-border-100" />
|
||||
</div>
|
||||
<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">
|
||||
<GoogleOAuthButton text={`${oauthProviderButtonText} Google`} />
|
||||
</div>
|
||||
)}
|
||||
{instance?.config?.is_github_enabled && <GithubOAuthButton text={`${oauthProviderButtonText} Github`} />}
|
||||
{config?.is_github_enabled && <GithubOAuthButton text={`${oauthProviderButtonText} Github`} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -62,7 +62,7 @@ export const ImagePickerPopover: React.FC<Props> = 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<Props> = 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 () => {
|
||||
|
@ -30,7 +30,7 @@ export const UserImageUploadModal: React.FC<Props> = observer((props) => {
|
||||
const [image, setImage] = useState<File | null>(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<Props> = 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,
|
||||
});
|
||||
|
||||
|
@ -34,7 +34,7 @@ export const WorkspaceImageUploadModal: React.FC<Props> = 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<Props> = 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,
|
||||
});
|
||||
|
||||
|
@ -14,12 +14,12 @@ type Props = {
|
||||
|
||||
export const GithubAuth: React.FC<Props> = 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 (
|
||||
|
@ -46,7 +46,7 @@ export const SingleIntegrationCard: React.FC<Props> = 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<Props> = 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(
|
||||
|
@ -22,7 +22,7 @@ const appInstallationService = new AppInstallationService();
|
||||
|
||||
export const SelectChannel: React.FC<Props> = observer(({ integration }) => {
|
||||
// store hooks
|
||||
const { instance } = useInstance();
|
||||
const { config } = useInstance();
|
||||
// states
|
||||
const [slackChannelAvailabilityToggle, setSlackChannelAvailabilityToggle] = useState<boolean>(false);
|
||||
const [slackChannel, setSlackChannel] = useState<ISlackIntegration | null>(null);
|
||||
@ -35,7 +35,7 @@ export const SelectChannel: React.FC<Props> = 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(
|
||||
|
@ -21,7 +21,7 @@ type Props = {
|
||||
export const IssueAttachmentUpload: React.FC<Props> = 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<Props> = 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;
|
||||
|
@ -116,7 +116,7 @@ export const IssueFormRoot: FC<IssueFormProps> = 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<IssueFormProps> = observer((props) => {
|
||||
) : (
|
||||
<>
|
||||
<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
|
||||
type="button"
|
||||
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>
|
||||
)}
|
||||
{instance?.config?.has_openai_configured && (
|
||||
{config?.has_openai_configured && (
|
||||
<GptAssistantPopover
|
||||
isOpen={gptAssistantModal}
|
||||
projectId={projectId}
|
||||
|
@ -30,7 +30,7 @@ export const PageExtraOptions: React.FC<Props> = 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<Props> = observer((props) => {
|
||||
<span>Archived at {renderFormattedDate(archived_at)}</span>
|
||||
</div>
|
||||
)}
|
||||
{isContentEditable && instance?.config?.has_openai_configured && (
|
||||
{isContentEditable && config?.has_openai_configured && (
|
||||
<GptAssistantPopover
|
||||
isOpen={gptModalOpen}
|
||||
projectId={projectId}
|
||||
|
@ -32,7 +32,7 @@ export interface IAppProvider {
|
||||
export const AppProvider: FC<IAppProvider> = 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<IAppProvider> = 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}
|
||||
>
|
||||
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
|
||||
</PostHogProvider>
|
||||
|
@ -32,7 +32,7 @@ export const InstanceWrapper: FC<TInstanceWrapper> = 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 <InstanceNotReady />;
|
||||
if (instance?.is_setup_done === false) return <InstanceNotReady />;
|
||||
|
||||
return <>{children}</>;
|
||||
});
|
||||
|
@ -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<IInstance> {
|
||||
async getInstanceInfo(): Promise<IInstanceInfo> {
|
||||
return this.get("/api/instances/")
|
||||
.then((response) => response.data)
|
||||
.catch((error) => {
|
||||
|
@ -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<void>;
|
||||
@ -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(() => {
|
||||
|
Loading…
Reference in New Issue
Block a user