forked from github/plane
fix: instance admin layout
This commit is contained in:
parent
564625ee22
commit
69ce0031d0
@ -1,9 +1,11 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
// layouts
|
import { Metadata } from "next";
|
||||||
import { AdminLayout } from "@/layouts/admin-layout";
|
import { AdminLayout } from "@/layouts/admin-layout";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "AI Settings - God Mode",
|
||||||
|
};
|
||||||
|
|
||||||
export default function AILayout({ children }: { children: ReactNode }) {
|
export default function AILayout({ children }: { children: ReactNode }) {
|
||||||
return <AdminLayout>{children}</AdminLayout>;
|
return <AdminLayout>{children}</AdminLayout>;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
// layouts
|
import { Metadata } from "next";
|
||||||
import { AdminLayout } from "@/layouts/admin-layout";
|
import { AdminLayout } from "@/layouts/admin-layout";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Authentication Settings - God Mode",
|
||||||
|
};
|
||||||
|
|
||||||
export default function AuthenticationLayout({ children }: { children: ReactNode }) {
|
export default function AuthenticationLayout({ children }: { children: ReactNode }) {
|
||||||
return <AdminLayout>{children}</AdminLayout>;
|
return <AdminLayout>{children}</AdminLayout>;
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
// layouts
|
import { Metadata } from "next";
|
||||||
import { AdminLayout } from "@/layouts/admin-layout";
|
import { AdminLayout } from "@/layouts/admin-layout";
|
||||||
|
|
||||||
interface EmailLayoutProps {
|
interface EmailLayoutProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Email Settings - God Mode",
|
||||||
|
};
|
||||||
|
|
||||||
const EmailLayout = ({ children }: EmailLayoutProps) => <AdminLayout>{children}</AdminLayout>;
|
const EmailLayout = ({ children }: EmailLayoutProps) => <AdminLayout>{children}</AdminLayout>;
|
||||||
|
|
||||||
export default EmailLayout;
|
export default EmailLayout;
|
||||||
|
@ -13,7 +13,7 @@ import { ControllerInput } from "@/components/common";
|
|||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
export interface IGeneralConfigurationForm {
|
export interface IGeneralConfigurationForm {
|
||||||
instance: IInstance["instance"];
|
instance: IInstance;
|
||||||
instanceAdmins: IInstanceAdmin[];
|
instanceAdmins: IInstanceAdmin[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,15 +26,15 @@ export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = observer(
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
} = useForm<Partial<IInstance["instance"]>>({
|
} = useForm<Partial<IInstance>>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
instance_name: instance?.instance_name,
|
instance_name: instance?.instance_name,
|
||||||
is_telemetry_enabled: instance?.is_telemetry_enabled,
|
is_telemetry_enabled: instance?.is_telemetry_enabled,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = async (formData: Partial<IInstance["instance"]>) => {
|
const onSubmit = async (formData: Partial<IInstance>) => {
|
||||||
const payload: Partial<IInstance["instance"]> = { ...formData };
|
const payload: Partial<IInstance> = { ...formData };
|
||||||
|
|
||||||
console.log("payload", payload);
|
console.log("payload", payload);
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
// components
|
|
||||||
import { AdminLayout } from "@/layouts/admin-layout";
|
import { AdminLayout } from "@/layouts/admin-layout";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
@ -7,7 +7,7 @@ import { GeneralConfigurationForm } from "./form";
|
|||||||
|
|
||||||
function GeneralPage() {
|
function GeneralPage() {
|
||||||
const { instance, instanceAdmins } = useInstance();
|
const { instance, instanceAdmins } = useInstance();
|
||||||
console.log("instance", instanceAdmins);
|
console.log("instance", instance);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="relative container mx-auto w-full h-full p-8 py-4 space-y-6 flex flex-col">
|
<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>
|
</div>
|
||||||
<div className="flex-grow overflow-hidden overflow-y-auto">
|
<div className="flex-grow overflow-hidden overflow-y-auto">
|
||||||
{instance?.instance && instanceAdmins && (
|
{instance && instanceAdmins && (
|
||||||
<GeneralConfigurationForm instance={instance.instance} instanceAdmins={instanceAdmins} />
|
<GeneralConfigurationForm instance={instance} instanceAdmins={instanceAdmins} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
// layouts
|
import { Metadata } from "next";
|
||||||
import { AdminLayout } from "@/layouts/admin-layout";
|
import { AdminLayout } from "@/layouts/admin-layout";
|
||||||
|
|
||||||
interface ImageLayoutProps {
|
interface ImageLayoutProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Images Settings - God Mode",
|
||||||
|
};
|
||||||
|
|
||||||
const ImageLayout = ({ children }: ImageLayoutProps) => <AdminLayout>{children}</AdminLayout>;
|
const ImageLayout = ({ children }: ImageLayoutProps) => <AdminLayout>{children}</AdminLayout>;
|
||||||
|
|
||||||
export default ImageLayout;
|
export default ImageLayout;
|
||||||
|
@ -1,40 +1,20 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { Metadata } from "next";
|
import { ThemeProvider } from "next-themes";
|
||||||
// components
|
import { SWRConfig } from "swr";
|
||||||
import { InstanceFailureView, InstanceSetupForm } from "@/components/instance";
|
// constants
|
||||||
|
import { SWR_CONFIG } from "@/constants/swr-config";
|
||||||
// helpers
|
// helpers
|
||||||
import { ASSET_PREFIX } from "@/helpers/common.helper";
|
import { ASSET_PREFIX } from "@/helpers/common.helper";
|
||||||
// layout
|
|
||||||
import { DefaultLayout } from "@/layouts/default-layout";
|
|
||||||
// lib
|
// 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
|
// styles
|
||||||
import "./globals.css";
|
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 (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@ -45,28 +25,18 @@ export default async function RootLayout({ children }: { children: ReactNode })
|
|||||||
<link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} />
|
<link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} />
|
||||||
</head>
|
</head>
|
||||||
<body className={`antialiased`}>
|
<body className={`antialiased`}>
|
||||||
<AppProvider initialState={{ instance: instanceDetails }}>
|
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
||||||
{instanceDetails ? (
|
<SWRConfig value={SWR_CONFIG}>
|
||||||
<>
|
<StoreProvider>
|
||||||
{instanceDetails?.instance?.is_setup_done ? (
|
<InstanceProvider>
|
||||||
<>{children}</>
|
<UserProvider>{children}</UserProvider>
|
||||||
) : (
|
</InstanceProvider>
|
||||||
<DefaultLayout>
|
</StoreProvider>
|
||||||
<div className="relative w-screen min-h-screen overflow-y-auto px-5 py-10 mx-auto flex justify-center items-center">
|
</SWRConfig>
|
||||||
<InstanceSetupForm />
|
</ThemeProvider>
|
||||||
</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>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default RootLayout;
|
||||||
|
@ -1,7 +1,26 @@
|
|||||||
|
import { Metadata } from "next";
|
||||||
|
// components
|
||||||
import { InstanceSignInForm } from "@/components/login";
|
import { InstanceSignInForm } from "@/components/login";
|
||||||
// layouts
|
// layouts
|
||||||
import { DefaultLayout } from "@/layouts/default-layout";
|
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() {
|
export default async function LoginPage() {
|
||||||
return (
|
return (
|
||||||
<DefaultLayout>
|
<DefaultLayout>
|
||||||
|
@ -6,3 +6,4 @@ export * from "./password-strength-meter";
|
|||||||
export * from "./banner";
|
export * from "./banner";
|
||||||
export * from "./empty-state";
|
export * from "./empty-state";
|
||||||
export * from "./logo-spinner";
|
export * from "./logo-spinner";
|
||||||
|
export * from "./toast";
|
||||||
|
@ -11,7 +11,7 @@ export const LogoSpinner = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center">
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -43,7 +43,7 @@ export const PasswordStrengthMeter: React.FC<Props> = (props: Props) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full p-1">
|
<div className="w-full">
|
||||||
<div className="flex w-full gap-1.5">
|
<div className="flex w-full gap-1.5">
|
||||||
{bars.map((color, index) => (
|
{bars.map((color, index) => (
|
||||||
<div key={index} className={cn("w-full h-1 rounded-full", color)} />
|
<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)} />;
|
||||||
|
};
|
@ -17,11 +17,11 @@ import TakeoffIconDark from "/public/logos/takeoff-icon-dark.svg";
|
|||||||
export const NewUserPopup: React.FC = observer(() => {
|
export const NewUserPopup: React.FC = observer(() => {
|
||||||
// hooks
|
// hooks
|
||||||
const { isNewUserPopup, toggleNewUserPopup } = useTheme();
|
const { isNewUserPopup, toggleNewUserPopup } = useTheme();
|
||||||
const { instance } = useInstance();
|
const { config } = useInstance();
|
||||||
// theme
|
// theme
|
||||||
const { resolvedTheme } = nextUseTheme();
|
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 <></>;
|
if (!isNewUserPopup) return <></>;
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
// store
|
// store
|
||||||
import { StoreContext } from "@/lib/app-providers";
|
import { StoreContext } from "@/lib/store-provider";
|
||||||
import { IInstanceStore } from "@/store/instance.store";
|
import { IInstanceStore } from "@/store/instance.store";
|
||||||
|
|
||||||
export const useInstance = (): IInstanceStore => {
|
export const useInstance = (): IInstanceStore => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
// store
|
// store
|
||||||
import { StoreContext } from "@/lib/app-providers";
|
import { StoreContext } from "@/lib/store-provider";
|
||||||
import { IThemeStore } from "@/store/theme.store";
|
import { IThemeStore } from "@/store/theme.store";
|
||||||
|
|
||||||
export const useTheme = (): IThemeStore => {
|
export const useTheme = (): IThemeStore => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
// store
|
// store
|
||||||
import { StoreContext } from "@/lib/app-providers";
|
import { StoreContext } from "@/lib/store-provider";
|
||||||
import { IUserStore } from "@/store/user.store";
|
import { IUserStore } from "@/store/user.store";
|
||||||
|
|
||||||
export const useUser = (): IUserStore => {
|
export const useUser = (): IUserStore => {
|
||||||
|
@ -2,14 +2,13 @@
|
|||||||
import { FC, ReactNode, useEffect } from "react";
|
import { FC, ReactNode, useEffect } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import useSWR from "swr";
|
|
||||||
// components
|
// components
|
||||||
import { InstanceSidebar } from "@/components/admin-sidebar";
|
import { InstanceSidebar } from "@/components/admin-sidebar";
|
||||||
import { InstanceHeader } from "@/components/auth-header";
|
import { InstanceHeader } from "@/components/auth-header";
|
||||||
import { LogoSpinner } from "@/components/common";
|
import { LogoSpinner } from "@/components/common";
|
||||||
import { NewUserPopup } from "@/components/new-user-popup";
|
import { NewUserPopup } from "@/components/new-user-popup";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance, useUser } from "@/hooks/store";
|
import { useUser } from "@/hooks/store";
|
||||||
|
|
||||||
type TAdminLayout = {
|
type TAdminLayout = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -19,15 +18,7 @@ export const AdminLayout: FC<TAdminLayout> = observer((props) => {
|
|||||||
const { children } = props;
|
const { children } = props;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// hooks
|
const { isUserLoggedIn } = useUser();
|
||||||
const { fetchInstanceAdmins } = useInstance();
|
|
||||||
const { fetchCurrentUser, isUserLoggedIn } = useUser();
|
|
||||||
|
|
||||||
useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins());
|
|
||||||
|
|
||||||
useSWR("CURRENT_USER", () => fetchCurrentUser(), {
|
|
||||||
shouldRetryOnError: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isUserLoggedIn === false) {
|
if (isUserLoggedIn === false) {
|
||||||
@ -48,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 />
|
||||||
<div className="h-full w-full overflow-hidden">{children}</div>
|
|
||||||
</main>
|
</main>
|
||||||
|
<div className="h-full w-full overflow-hidden">{children}</div>
|
||||||
<NewUserPopup />
|
<NewUserPopup />
|
||||||
</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 w-screen min-h-[500px] overflow-y-auto px-5 mx-auto flex justify-center items-center">
|
||||||
|
<InstanceFailureView />
|
||||||
|
</div>
|
||||||
|
</DefaultLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!instance?.is_setup_done) {
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
});
|
@ -1,11 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ReactNode, createContext } from "react";
|
import { ReactNode, createContext } from "react";
|
||||||
import { ThemeProvider } from "next-themes";
|
|
||||||
// store
|
// store
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
// store initialization
|
|
||||||
import { AppWrapper } from "./app-wrapper";
|
|
||||||
|
|
||||||
let rootStore = new RootStore();
|
let rootStore = new RootStore();
|
||||||
|
|
||||||
@ -25,19 +22,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 <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
|
||||||
return (
|
|
||||||
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
|
||||||
<StoreContext.Provider value={store}>
|
|
||||||
<AppWrapper>{children}</AppWrapper>
|
|
||||||
</StoreContext.Provider>
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
};
|
};
|
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
|
// types
|
||||||
import type { IFormattedInstanceConfiguration, IInstance, IInstanceAdmin, IInstanceConfiguration } from "@plane/types";
|
import type {
|
||||||
|
IFormattedInstanceConfiguration,
|
||||||
|
IInstance,
|
||||||
|
IInstanceAdmin,
|
||||||
|
IInstanceConfiguration,
|
||||||
|
IInstanceInfo,
|
||||||
|
} from "@plane/types";
|
||||||
// helpers
|
// helpers
|
||||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||||
import { APIService } from "@/services/api.service";
|
import { APIService } from "@/services/api.service";
|
||||||
@ -9,8 +15,8 @@ export class InstanceService extends APIService {
|
|||||||
super(API_BASE_URL);
|
super(API_BASE_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getInstanceInfo(): Promise<IInstance> {
|
async getInstanceInfo(): Promise<IInstanceInfo> {
|
||||||
return this.get<IInstance>("/api/instances/")
|
return this.get<IInstanceInfo>("/api/instances/")
|
||||||
.then((response) => response.data)
|
.then((response) => response.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
@ -25,8 +31,8 @@ export class InstanceService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateInstanceInfo(data: Partial<IInstance["instance"]>): Promise<IInstance["instance"]> {
|
async updateInstanceInfo(data: Partial<IInstance>): Promise<IInstance> {
|
||||||
return this.patch<Partial<IInstance["instance"]>, IInstance["instance"]>("/api/instances/", data)
|
return this.patch<Partial<IInstance>, IInstance>("/api/instances/", data)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import set from "lodash/set";
|
import set from "lodash/set";
|
||||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
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
|
// helpers
|
||||||
import { EInstanceStatus, TInstanceStatus } from "@/helpers";
|
import { EInstanceStatus, TInstanceStatus } from "@/helpers";
|
||||||
// services
|
// services
|
||||||
@ -11,16 +18,18 @@ import { RootStore } from "@/store/root.store";
|
|||||||
export interface IInstanceStore {
|
export interface IInstanceStore {
|
||||||
// issues
|
// issues
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
error: any;
|
||||||
instanceStatus: TInstanceStatus | undefined;
|
instanceStatus: TInstanceStatus | undefined;
|
||||||
instance: IInstance | undefined;
|
instance: IInstance | undefined;
|
||||||
|
config: IInstanceConfig | undefined;
|
||||||
instanceAdmins: IInstanceAdmin[] | undefined;
|
instanceAdmins: IInstanceAdmin[] | undefined;
|
||||||
instanceConfigurations: IInstanceConfiguration[] | undefined;
|
instanceConfigurations: IInstanceConfiguration[] | undefined;
|
||||||
// computed
|
// computed
|
||||||
formattedConfig: IFormattedInstanceConfiguration | undefined;
|
formattedConfig: IFormattedInstanceConfiguration | undefined;
|
||||||
// action
|
// action
|
||||||
hydrate: (data: any) => void;
|
hydrate: (data: IInstanceInfo) => void;
|
||||||
fetchInstanceInfo: () => Promise<IInstance | undefined>;
|
fetchInstanceInfo: () => Promise<IInstanceInfo | undefined>;
|
||||||
updateInstanceInfo: (data: Partial<IInstance["instance"]>) => Promise<IInstance["instance"] | undefined>;
|
updateInstanceInfo: (data: Partial<IInstance>) => Promise<IInstance | undefined>;
|
||||||
fetchInstanceAdmins: () => Promise<IInstanceAdmin[] | undefined>;
|
fetchInstanceAdmins: () => Promise<IInstanceAdmin[] | undefined>;
|
||||||
fetchInstanceConfigurations: () => Promise<IInstanceConfiguration[] | undefined>;
|
fetchInstanceConfigurations: () => Promise<IInstanceConfiguration[] | undefined>;
|
||||||
updateInstanceConfigurations: (data: Partial<IFormattedInstanceConfiguration>) => Promise<IInstanceConfiguration[]>;
|
updateInstanceConfigurations: (data: Partial<IFormattedInstanceConfiguration>) => Promise<IInstanceConfiguration[]>;
|
||||||
@ -28,8 +37,10 @@ export interface IInstanceStore {
|
|||||||
|
|
||||||
export class InstanceStore implements IInstanceStore {
|
export class InstanceStore implements IInstanceStore {
|
||||||
isLoading: boolean = true;
|
isLoading: boolean = true;
|
||||||
|
error: any = undefined;
|
||||||
instanceStatus: TInstanceStatus | undefined = undefined;
|
instanceStatus: TInstanceStatus | undefined = undefined;
|
||||||
instance: IInstance | undefined = undefined;
|
instance: IInstance | undefined = undefined;
|
||||||
|
config: IInstanceConfig | undefined = undefined;
|
||||||
instanceAdmins: IInstanceAdmin[] | undefined = undefined;
|
instanceAdmins: IInstanceAdmin[] | undefined = undefined;
|
||||||
instanceConfigurations: IInstanceConfiguration[] | undefined = undefined;
|
instanceConfigurations: IInstanceConfiguration[] | undefined = undefined;
|
||||||
// service
|
// service
|
||||||
@ -39,6 +50,7 @@ export class InstanceStore implements IInstanceStore {
|
|||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// observable
|
// observable
|
||||||
isLoading: observable.ref,
|
isLoading: observable.ref,
|
||||||
|
error: observable.ref,
|
||||||
instanceStatus: observable,
|
instanceStatus: observable,
|
||||||
instance: observable,
|
instance: observable,
|
||||||
instanceAdmins: observable,
|
instanceAdmins: observable,
|
||||||
@ -57,8 +69,11 @@ export class InstanceStore implements IInstanceStore {
|
|||||||
this.instanceService = new InstanceService();
|
this.instanceService = new InstanceService();
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrate = (data: any) => {
|
hydrate = (data: IInstanceInfo) => {
|
||||||
if (data) this.instance = data;
|
if (data) {
|
||||||
|
this.instance = data.instance;
|
||||||
|
this.config = data.config;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,17 +95,22 @@ export class InstanceStore implements IInstanceStore {
|
|||||||
fetchInstanceInfo = async () => {
|
fetchInstanceInfo = async () => {
|
||||||
try {
|
try {
|
||||||
if (this.instance === undefined) this.isLoading = true;
|
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
|
// 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(() => {
|
runInAction(() => {
|
||||||
|
console.log("instanceInfo: ", instanceInfo);
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.instance = instance;
|
this.instance = instanceInfo.instance;
|
||||||
|
this.config = instanceInfo.config;
|
||||||
});
|
});
|
||||||
return instance;
|
return instanceInfo;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching the instance info");
|
console.error("Error fetching the instance info");
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
this.error = { message: "Failed to fetch the instance info" };
|
||||||
this.instanceStatus = {
|
this.instanceStatus = {
|
||||||
status: EInstanceStatus.ERROR,
|
status: EInstanceStatus.ERROR,
|
||||||
};
|
};
|
||||||
@ -100,10 +120,10 @@ export class InstanceStore implements IInstanceStore {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @description updating instance information
|
* @description updating instance information
|
||||||
* @param {Partial<IInstance["instance"]>} data
|
* @param {Partial<IInstance>} data
|
||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
updateInstanceInfo = async (data: Partial<IInstance["instance"]>) => {
|
updateInstanceInfo = async (data: Partial<IInstance>) => {
|
||||||
try {
|
try {
|
||||||
const instanceResponse = await this.instanceService.updateInstanceInfo(data);
|
const instanceResponse = await this.instanceService.updateInstanceInfo(data);
|
||||||
if (instanceResponse) {
|
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,
|
TInstanceAuthenticationKeys,
|
||||||
} from "./";
|
} from "./";
|
||||||
|
|
||||||
|
export interface IInstanceInfo {
|
||||||
|
instance: IInstance;
|
||||||
|
config: IInstanceConfig;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IInstance {
|
export interface IInstance {
|
||||||
instance: {
|
id: string;
|
||||||
id: string;
|
created_at: string;
|
||||||
created_at: string;
|
updated_at: string;
|
||||||
updated_at: string;
|
instance_name: string | undefined;
|
||||||
instance_name: string | undefined;
|
whitelist_emails: string | undefined;
|
||||||
whitelist_emails: string | undefined;
|
instance_id: string | undefined;
|
||||||
instance_id: string | undefined;
|
license_key: string | undefined;
|
||||||
license_key: string | undefined;
|
api_key: string | undefined;
|
||||||
api_key: string | undefined;
|
version: string | undefined;
|
||||||
version: string | undefined;
|
last_checked_at: string | undefined;
|
||||||
last_checked_at: string | undefined;
|
namespace: string | undefined;
|
||||||
namespace: string | undefined;
|
is_telemetry_enabled: boolean;
|
||||||
is_telemetry_enabled: boolean;
|
is_support_required: boolean;
|
||||||
is_support_required: boolean;
|
is_activated: boolean;
|
||||||
is_activated: boolean;
|
is_setup_done: boolean;
|
||||||
is_setup_done: boolean;
|
is_signup_screen_visited: boolean;
|
||||||
is_signup_screen_visited: boolean;
|
user_count: number | undefined;
|
||||||
user_count: number | undefined;
|
is_verified: boolean;
|
||||||
is_verified: boolean;
|
created_by: string | undefined;
|
||||||
created_by: string | undefined;
|
updated_by: string | undefined;
|
||||||
updated_by: string | undefined;
|
workspaces_exist: boolean;
|
||||||
workspaces_exist: boolean;
|
}
|
||||||
};
|
|
||||||
config: {
|
export interface IInstanceConfig {
|
||||||
is_google_enabled: boolean;
|
is_google_enabled: boolean;
|
||||||
is_github_enabled: boolean;
|
is_github_enabled: boolean;
|
||||||
is_magic_login_enabled: boolean;
|
is_magic_login_enabled: boolean;
|
||||||
is_email_password_enabled: boolean;
|
is_email_password_enabled: boolean;
|
||||||
github_app_name: string | undefined;
|
github_app_name: string | undefined;
|
||||||
slack_client_id: string | undefined;
|
slack_client_id: string | undefined;
|
||||||
posthog_api_key: string | undefined;
|
posthog_api_key: string | undefined;
|
||||||
posthog_host: string | undefined;
|
posthog_host: string | undefined;
|
||||||
has_unsplash_configured: boolean;
|
has_unsplash_configured: boolean;
|
||||||
has_openai_configured: boolean;
|
has_openai_configured: boolean;
|
||||||
file_size_limit: number | undefined;
|
file_size_limit: number | undefined;
|
||||||
is_smtp_configured: boolean;
|
is_smtp_configured: boolean;
|
||||||
app_base_url: string | undefined;
|
app_base_url: string | undefined;
|
||||||
space_base_url: string | undefined;
|
space_base_url: string | undefined;
|
||||||
admin_base_url: string | undefined;
|
admin_base_url: string | undefined;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IInstanceAdmin {
|
export interface IInstanceAdmin {
|
||||||
|
Loading…
Reference in New Issue
Block a user