fix: god mode changes

This commit is contained in:
sriram veeraghanta 2024-01-31 14:03:52 +05:30
parent c9d2ea36b8
commit 8445cef3b4
38 changed files with 3996 additions and 41 deletions

72
god-mode/app/ai/page.tsx Normal file
View File

@ -0,0 +1,72 @@
"use client";
import { ReactElement } from "react";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
// layouts
// import { InstanceAdminLayout } from "layouts/admin-layout";
// types
// import { NextPageWithLayout } from "lib/types";
// hooks
// import { useApplication } from "hooks/store";
// ui
import { Loader } from "@plane/ui";
// icons
import { Lightbulb } from "lucide-react";
// components
// import { InstanceAIForm } from "components/instance";
const InstanceAIPage = observer(() => {
// store
// const {
// instance: { fetchInstanceConfigurations, formattedConfig },
// } = useApplication();
// useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
return (
<div className="flex flex-col gap-8">
<div className="mb-2 border-b border-custom-border-100 pb-3">
<div className="pb-1 text-xl font-medium text-custom-text-100">
AI features for all your workspaces
</div>
<div className="text-sm font-normal text-custom-text-300">
Configure your AI API credentials so Plane AI features are turned on
for all your workspaces.
</div>
</div>
{/* {formattedConfig ? (
<>
<div>
<div className="pb-1 text-xl font-medium text-custom-text-100">
OpenAI
</div>
<div className="text-sm font-normal text-custom-text-300">
If you use ChatGPT, this is for you.
</div>
</div>
<InstanceAIForm config={formattedConfig} />
<div className="my-2 flex">
<div className="flex items-center gap-2 rounded border border-custom-primary-100/20 bg-custom-primary-100/10 px-4 py-2 text-xs text-custom-primary-200">
<Lightbulb height="14" width="14" />
<div>
If you have a preferred AI models vendor, please get in touch
with us.
</div>
</div>
</div>
</>
) : (
<Loader className="space-y-4">
<div className="grid grid-cols-2 gap-x-8 gap-y-4">
<Loader.Item height="50px" />
<Loader.Item height="50px" />
</div>
<Loader.Item height="50px" />
</Loader>
)} */}
</div>
);
});
export default InstanceAIPage;

View File

@ -0,0 +1,174 @@
"use client";
import { ReactElement, useState } from "react";
import Link from "next/link";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
// layouts
// import { InstanceAdminLayout } from "layouts/admin-layout";
// types
// import { NextPageWithLayout } from "lib/types";
// hooks
// import { useApplication } from "hooks/store";
// hooks
// import useToast from "hooks/use-toast";
// ui
import { Loader, ToggleSwitch } from "@plane/ui";
// components
// import {
// InstanceGithubConfigForm,
// InstanceGoogleConfigForm,
// } from "components/instance";
const InstanceAuthorizationPage = observer(() => {
// store
// const {
// instance: {
// fetchInstanceConfigurations,
// formattedConfig,
// updateInstanceConfigurations,
// },
// } = useApplication();
// useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
// toast
// const { setToastAlert } = useToast();
// state
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
// const enableSignup = formattedConfig?.ENABLE_SIGNUP ?? "0";
// const enableMagicLogin = formattedConfig?.ENABLE_MAGIC_LINK_LOGIN ?? "0";
// const enableEmailPassword = formattedConfig?.ENABLE_EMAIL_PASSWORD ?? "0";
// const updateConfig = async (
// key: "ENABLE_SIGNUP" | "ENABLE_MAGIC_LINK_LOGIN" | "ENABLE_EMAIL_PASSWORD",
// value: string
// ) => {
// setIsSubmitting(true);
// const payload = {
// [key]: value,
// };
// await updateInstanceConfigurations(payload)
// .then(() => {
// setToastAlert({
// title: "Success",
// type: "success",
// message: "SSO and OAuth Settings updated successfully",
// });
// setIsSubmitting(false);
// })
// .catch((err) => {
// console.error(err);
// setToastAlert({
// title: "Error",
// type: "error",
// message: "Failed to update SSO and OAuth Settings",
// });
// setIsSubmitting(false);
// });
// };
return (
<div className="flex flex-col gap-8">
<div className="mb-2 border-b border-custom-border-100 pb-3">
<div className="pb-1 text-xl font-medium text-custom-text-100">
Single sign-on and OAuth
</div>
<div className="text-sm font-normal text-custom-text-300">
Make your teams life easy by letting them sign-up with their Google
and GitHub accounts, and below are the settings.
</div>
</div>
{/* {formattedConfig ? (
<>
<div className="flex w-full flex-col gap-12 border-b border-custom-border-100 pb-8 lg:w-2/5">
<div className="pointer-events-none mr-4 flex items-center gap-14 opacity-50">
<div className="grow">
<div className="text-sm font-medium text-custom-text-100">
Turn Magic Links{" "}
{Boolean(parseInt(enableMagicLogin)) ? "off" : "on"}
</div>
<div className="text-xs font-normal text-custom-text-300">
<p>Slack-like emails for authentication.</p>
You need to have set up email{" "}
<Link href="email">
<span className="text-custom-primary-100 hover:underline">
here
</span>
</Link>{" "}
to enable this.
</div>
</div>
<div className={`shrink-0 ${isSubmitting && "opacity-70"}`}>
<ToggleSwitch
value={Boolean(parseInt(enableMagicLogin))}
onChange={() => {}}
size="sm"
disabled={isSubmitting}
/>
</div>
</div>
<div className="mr-4 flex items-center gap-14">
<div className="grow">
<div className="text-sm font-medium text-custom-text-100">
Let your users log in via the methods below
</div>
<div className="text-xs font-normal text-custom-text-300">
Toggling this off will disable all previous configs. Users
will only be able to login with an e-mail and password combo.
</div>
</div>
<div className={`shrink-0 ${isSubmitting && "opacity-70"}`}>
<ToggleSwitch
value={Boolean(parseInt(enableSignup))}
onChange={() => {
Boolean(parseInt(enableSignup)) === true
? updateConfig("ENABLE_SIGNUP", "0")
: updateConfig("ENABLE_SIGNUP", "1");
}}
size="sm"
disabled={isSubmitting}
/>
</div>
</div>
</div>
<div className="flex flex-col gap-y-6 py-2">
<div className="w-full">
<div className="flex items-center justify-between border-b border-custom-border-100 py-2">
<span className="text-lg font-medium tracking-tight">
Google
</span>
</div>
<div className="px-2 py-6">
<InstanceGoogleConfigForm config={formattedConfig} />
</div>
</div>
<div className="w-full">
<div className="flex items-center justify-between border-b border-custom-border-100 py-2">
<span className="text-lg font-medium tracking-tight">
Github
</span>
</div>
<div className="px-2 py-6">
<InstanceGithubConfigForm config={formattedConfig} />
</div>
</div>
</div>
</>
) : (
<Loader className="space-y-4">
<div className="grid grid-cols-2 gap-x-8 gap-y-4">
<Loader.Item height="50px" />
<Loader.Item height="50px" />
</div>
<Loader.Item height="50px" />
</Loader>
)} */}
</div>
);
});
export default InstanceAuthorizationPage;

View File

@ -0,0 +1,56 @@
"use client";
import { ReactElement } from "react";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
// layouts
// import { InstanceAdminLayout } from "layouts/admin-layout";
// hooks
// import { useApplication } from "hooks/store";
// ui
import { Loader } from "@plane/ui";
// components
// import { InstanceEmailForm } from "components/instance";
const InstanceEmailPage = observer(() => {
// store
// const {
// instance: { fetchInstanceConfigurations, formattedConfig },
// } = useApplication();
// useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
return (
<div className="flex flex-col gap-8">
<div className="mb-2 border-b border-custom-border-100 pb-3">
<div className="pb-1 text-xl font-medium text-custom-text-100">
Secure emails from your own instance
</div>
<div className="text-sm font-normal text-custom-text-300">
Plane can send useful emails to you and your users from your own
instance without talking to the Internet.
</div>
<div className="text-sm font-normal text-custom-text-300">
Set it up below and please test your settings before you save them.{" "}
<span className="text-red-400">
Misconfigs can lead to email bounces and errors.
</span>
</div>
</div>
{/* {formattedConfig ? (
<InstanceEmailForm config={formattedConfig} />
) : (
<Loader className="space-y-4">
<div className="grid grid-cols-2 gap-x-8 gap-y-4">
<Loader.Item height="50px" />
<Loader.Item height="50px" />
</div>
<Loader.Item height="50px" />
</Loader>
)} */}
</div>
);
});
export default InstanceEmailPage;

6
god-mode/app/globals.css Normal file
View File

@ -0,0 +1,6 @@
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@48,400,0,0&display=swap");
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -1,4 +1,7 @@
"use client";
import { FC } from "react";
import { usePathname } from "next/navigation";
// mobx
import { observer } from "mobx-react-lite";
// ui
@ -6,12 +9,28 @@ import { Breadcrumbs } from "@plane/ui";
// icons
import { Settings } from "lucide-react";
export interface IInstanceAdminHeader {
title?: string;
}
export const InstanceHeader: FC = observer(() => {
const pathName = usePathname();
export const InstanceAdminHeader: FC<IInstanceAdminHeader> = observer((props) => {
const { title } = props;
const getHeaderTitle = () => {
if (pathName === "/") {
return "General";
}
if (pathName === "/ai") {
return "Artificial Intelligence";
}
if (pathName === "/email") {
return "Email";
}
if (pathName === "/authorization") {
return "Authorization";
}
if (pathName === "/image") {
return "Image";
}
return;
};
const title = getHeaderTitle();
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
@ -23,7 +42,7 @@ export const InstanceAdminHeader: FC<IInstanceAdminHeader> = observer((props) =>
type="text"
icon={<Settings className="h-4 w-4 text-custom-text-300" />}
label="Settings"
link="/god-mode"
link="/"
/>
<Breadcrumbs.BreadcrumbItem type="text" label={title} />
</Breadcrumbs>

View File

@ -0,0 +1,50 @@
"use client";
import { ReactElement } from "react";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
// layouts
// import { InstanceAdminLayout } from "layouts/admin-layout";
// types
// import { NextPageWithLayout } from "lib/types";
// hooks
// import { useApplication } from "hooks/store";
// ui
import { Loader } from "@plane/ui";
// components
// import { InstanceImageConfigForm } from "components/instance";
const InstanceImagePage = observer(() => {
// store
// const {
// instance: { fetchInstanceConfigurations, formattedConfig },
// } = useApplication();
// useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
return (
<div className="flex flex-col gap-8">
<div className="mb-2 border-b border-custom-border-100 pb-3">
<div className="pb-1 text-xl font-medium text-custom-text-100">
Third-party image libraries
</div>
<div className="text-sm font-normal text-custom-text-300">
Let your users search and choose images from third-party libraries
</div>
</div>
{/* {formattedConfig ? (
<InstanceImageConfigForm config={formattedConfig} />
) : (
<Loader className="space-y-4">
<div className="grid grid-cols-2 gap-x-8 gap-y-4">
<Loader.Item height="50px" />
<Loader.Item height="50px" />
</div>
<Loader.Item height="50px" />
</Loader>
)} */}
</div>
);
});
export default InstanceImagePage;

51
god-mode/app/layout.tsx Normal file
View File

@ -0,0 +1,51 @@
import "./globals.css";
import { ThemeProvider } from "lib/theme-provider";
// components
import { InstanceSidebar } from "./sidebar";
import { InstanceHeader } from "./header";
export const metadata = {
title: "God Mode",
description: "You are god now.",
};
interface RootLayoutProps {
children: React.ReactNode;
}
const fetchAdminInfo = async () => {
const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "";
const res = await fetch(BASE_URL + "/api/users/me/instance-admin/");
const data = await res.json();
return data;
};
export default async function RootLayout({ children }: RootLayoutProps) {
const response = await fetchAdminInfo();
console.log(response);
return (
<html lang="en">
<body className={`antialiased`}>
{/* <AuthWrapper> */}
{response?.is_instance_admin ? (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<div className="relative flex h-screen w-full overflow-hidden">
<InstanceSidebar />
<main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100">
<InstanceHeader />
<div className="h-full w-full overflow-hidden px-10 py-12">
<div className="relative h-full w-full overflow-x-hidden overflow-y-scroll">
{children}
</div>
</div>
</main>
</div>
</ThemeProvider>
) : (
<div>Login</div>
)}
</body>
</html>
);
}

9
god-mode/app/page.tsx Normal file
View File

@ -0,0 +1,9 @@
import { GeneralView } from "components/views";
export default function Home() {
return (
<div className="flex">
<GeneralView />
</div>
);
}

28
god-mode/app/sidebar.tsx Normal file
View File

@ -0,0 +1,28 @@
"use client";
import { FC } from "react";
// hooks
import { useAppTheme } from "hooks/useTheme";
// components
import { HelpSection, SidebarMenu, SidebarDropdown } from "components/sidebar";
export interface IInstanceSidebar {}
export const InstanceSidebar: FC<IInstanceSidebar> = () => {
// store
const { sidebarCollapsed } = useAppTheme();
return (
<div
className={`inset-y-0 z-20 flex h-full flex-shrink-0 flex-grow-0 flex-col border-r border-custom-sidebar-border-200 bg-custom-sidebar-background-100 duration-300 md:relative ${
sidebarCollapsed ? "" : "md:w-[280px]"
} ${sidebarCollapsed ? "left-0" : "-left-full md:left-0"}`}
>
<div className="flex h-full w-full flex-1 flex-col">
<SidebarDropdown />
<SidebarMenu />
<HelpSection />
</div>
</div>
);
};

View File

@ -0,0 +1,142 @@
import { FC, useState, useRef } from "react";
import { Transition } from "@headlessui/react";
import Link from "next/link";
import { FileText, HelpCircle, MessagesSquare, MoveLeft } from "lucide-react";
// hooks
import { useAppTheme } from "hooks/useTheme";
// icons
import { DiscordIcon, GithubIcon } from "@plane/ui";
// assets
import packageJson from "package.json";
const helpOptions = [
{
name: "Documentation",
href: "https://docs.plane.so/",
Icon: FileText,
},
{
name: "Join our Discord",
href: "https://discord.com/invite/A92xrEGCge",
Icon: DiscordIcon,
},
{
name: "Report a bug",
href: "https://github.com/makeplane/plane/issues/new/choose",
Icon: GithubIcon,
},
{
name: "Chat with us",
href: null,
onClick: () => (window as any).$crisp.push(["do", "chat:show"]),
Icon: MessagesSquare,
},
];
export const HelpSection: FC = () => {
// states
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
// store
const { sidebarCollapsed, toggleSidebar } = useAppTheme();
// refs
const helpOptionsRef = useRef<HTMLDivElement | null>(null);
return (
<div
className={`flex w-full items-center justify-between gap-1 self-baseline border-t border-custom-border-200 bg-custom-sidebar-background-100 px-4 py-2 ${
sidebarCollapsed ? "flex-col" : ""
}`}
>
<div
className={`flex items-center gap-1 ${
sidebarCollapsed ? "flex-col justify-center" : "w-full justify-end"
}`}
>
<button
type="button"
className={`grid place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${
sidebarCollapsed ? "w-full" : ""
}`}
onClick={() => setIsNeedHelpOpen((prev) => !prev)}
>
<HelpCircle className="h-3.5 w-3.5" />
</button>
<button
type="button"
className="grid place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:hidden"
onClick={() => toggleSidebar()}
>
<MoveLeft className="h-3.5 w-3.5" />
</button>
<button
type="button"
className={`hidden place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:grid ${
sidebarCollapsed ? "w-full" : ""
}`}
onClick={() => toggleSidebar()}
>
<MoveLeft
className={`h-3.5 w-3.5 duration-300 ${
sidebarCollapsed ? "rotate-180" : ""
}`}
/>
</button>
</div>
<div className="relative">
<Transition
show={isNeedHelpOpen}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<div
className={`absolute bottom-2 min-w-[10rem] ${
sidebarCollapsed ? "left-full" : "-left-[75px]"
} divide-y divide-custom-border-200 whitespace-nowrap rounded bg-custom-background-100 p-1 shadow-custom-shadow-xs`}
ref={helpOptionsRef}
>
<div className="space-y-1 pb-2">
{helpOptions.map(({ name, Icon, href, onClick }) => {
if (href)
return (
<Link href={href} key={name} target="_blank">
<div className="flex items-center gap-x-2 rounded px-2 py-1 text-xs hover:bg-custom-background-80">
<div className="grid flex-shrink-0 place-items-center">
<Icon
className="h-3.5 w-3.5 text-custom-text-200"
size={14}
/>
</div>
<span className="text-xs">{name}</span>
</div>
</Link>
);
else
return (
<button
key={name}
type="button"
onClick={onClick ?? undefined}
className="flex w-full items-center gap-x-2 rounded px-2 py-1 text-xs hover:bg-custom-background-80"
>
<div className="grid flex-shrink-0 place-items-center">
<Icon className="h-3.5 w-3.5 text-custom-text-200" />
</div>
<span className="text-xs">{name}</span>
</button>
);
})}
</div>
<div className="px-2 pb-1 pt-2 text-[10px]">
Version: v{packageJson.version}
</div>
</div>
</Transition>
</div>
</div>
);
};

View File

@ -0,0 +1,3 @@
export * from "./help-section";
export * from "./sidebar-menu";
export * from "./sidebar-dropdown";

View File

@ -0,0 +1,158 @@
import { Fragment } from "react";
import { useRouter } from "next/router";
import { useTheme } from "next-themes";
import { observer } from "mobx-react-lite";
import Link from "next/link";
// components
import { Menu, Transition } from "@headlessui/react";
// icons
import { LogIn, LogOut, Settings, UserCog2 } from "lucide-react";
// hooks
import { useAppTheme } from "hooks/useTheme";
// hooks
// import useToast from "hooks/use-toast";
// ui
import { Avatar, Tooltip } from "@plane/ui";
// Static Data
const PROFILE_LINKS = [
{
key: "settings",
name: "Settings",
icon: Settings,
link: `/profile`,
},
];
export const SidebarDropdown = observer(() => {
// router
// const router = useRouter();
// store hooks
const { sidebarCollapsed } = useAppTheme();
// const { signOut, currentUser, currentUserSettings } = useUser();
// hooks
// const { setToastAlert } = useToast();
const { setTheme } = useTheme();
// redirect url for normal mode
// const redirectWorkspaceSlug =
// workspaceSlug ||
// currentUserSettings?.workspace?.last_workspace_slug ||
// currentUserSettings?.workspace?.fallback_workspace_slug ||
// "";
const handleSignOut = async () => {
// await signOut()
// .then(() => {
// mutate("CURRENT_USER_DETAILS", null);
// setTheme("system");
// router.push("/");
// })
// .catch(() =>
// setToastAlert({
// type: "error",
// title: "Error!",
// message: "Failed to sign out. Please try again.",
// })
// );
};
return (
<div className="flex max-h-[3.75rem] items-center gap-x-5 gap-y-2 border-b border-custom-sidebar-border-200 px-4 py-3.5">
<div className="h-full w-full truncate">
<div
className={`flex flex-grow items-center gap-x-2 truncate rounded py-1 ${
sidebarCollapsed ? "justify-center" : ""
}`}
>
<div className="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded bg-custom-sidebar-background-80">
<UserCog2 className="h-5 w-5 text-custom-text-200" />
</div>
{!sidebarCollapsed && (
<div className="flex w-full gap-2">
<h4 className="grow truncate text-base font-medium text-custom-text-200">
Instance admin
</h4>
<Tooltip position="bottom-left" tooltipContent="Exit God Mode">
<div className="flex-shrink-0">
{/* <Link href={`/${redirectWorkspaceSlug}`}>
<span>
<LogIn className="h-5 w-5 rotate-180 text-custom-text-200" />
</span>
</Link> */}
</div>
</Tooltip>
</div>
)}
</div>
</div>
{!sidebarCollapsed && (
<Menu as="div" className="relative flex-shrink-0">
<Menu.Button className="grid place-items-center outline-none">
{/* <Avatar
name={currentUser?.display_name}
src={currentUser?.avatar}
size={24}
shape="square"
className="!text-base"
/> */}
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className="absolute left-0 z-20 mt-1.5 flex w-52 flex-col divide-y
divide-custom-sidebar-border-100 rounded-md border border-custom-sidebar-border-200 bg-custom-sidebar-background-100 px-1 py-2 text-xs shadow-lg outline-none"
>
<div className="flex flex-col gap-2.5 pb-2">
{/* <span className="px-2 text-custom-sidebar-text-200">
{currentUser?.email}
</span> */}
{PROFILE_LINKS.map((link) => (
<Menu.Item key={link.key} as="button" type="button">
<Link href={link.link}>
<span className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80">
<link.icon className="h-4 w-4 stroke-[1.5]" />
{link.name}
</span>
</Link>
</Menu.Item>
))}
</div>
<div className="py-2">
<Menu.Item
as="button"
type="button"
className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80"
onClick={handleSignOut}
>
<LogOut className="h-4 w-4 stroke-[1.5]" />
Sign out
</Menu.Item>
</div>
<div className="p-2 pb-0">
<Menu.Item as="button" type="button" className="w-full">
{/* <Link href={`/${redirectWorkspaceSlug}`}>
<span className="flex w-full items-center justify-center rounded bg-custom-primary-100/20 px-2 py-1 text-sm font-medium text-custom-primary-100 hover:bg-custom-primary-100/30 hover:text-custom-primary-200">
Exit God Mode
</span>
</Link> */}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
)}
</div>
);
});

View File

@ -0,0 +1,104 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
import { Image, BrainCog, Cog, Lock, Mail } from "lucide-react";
// hooks
import { useAppTheme } from "hooks/useTheme";
// ui
import { Tooltip } from "@plane/ui";
import { observer } from "mobx-react-lite";
const INSTANCE_ADMIN_LINKS = [
{
Icon: Cog,
name: "General",
description: "Identify your instances and get key details",
href: `/`,
},
{
Icon: Mail,
name: "Email",
description: "Set up emails to your users",
href: `/email`,
},
{
Icon: Lock,
name: "SSO and OAuth",
description: "Configure your Google and GitHub SSOs",
href: `/authorization`,
},
{
Icon: BrainCog,
name: "Artificial intelligence",
description: "Configure your OpenAI creds",
href: `/ai`,
},
{
Icon: Image,
name: "Images in Plane",
description: "Allow third-party image libraries",
href: `/image`,
},
];
export const SidebarMenu = observer(() => {
// store hooks
const { sidebarCollapsed } = useAppTheme();
// router
const pathName = usePathname();
return (
<div className="flex h-full w-full flex-col gap-2.5 overflow-y-auto px-4 py-6">
{INSTANCE_ADMIN_LINKS.map((item, index) => {
const isActive =
item.name === "Settings"
? pathName.includes(item.href)
: pathName === item.href;
return (
<Link key={index} href={item.href}>
<div>
<Tooltip
tooltipContent={item.name}
position="right"
className="ml-2"
disabled={!sidebarCollapsed}
>
<div
className={`group flex w-full items-center gap-3 rounded-md px-3 py-2 outline-none ${
isActive
? "bg-custom-primary-100/10 text-custom-primary-100"
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
} ${sidebarCollapsed ? "justify-center" : ""}`}
>
{<item.Icon className="h-4 w-4" />}
{!sidebarCollapsed && (
<div className="flex flex-col leading-snug">
<span
className={`text-sm font-medium ${
isActive
? "text-custom-primary-100"
: "text-custom-sidebar-text-200"
}`}
>
{item.name}
</span>
<span
className={`text-[10px] ${
isActive
? "text-custom-primary-90"
: "text-custom-sidebar-text-400"
}`}
>
{item.description}
</span>
</div>
)}
</div>
</Tooltip>
</div>
</Link>
);
})}
</div>
);
});

View File

@ -0,0 +1,30 @@
export const GeneralView = () => {
// const { instance, instanceAdmins } = useInstance();
return (
<div className="flex h-full w-full flex-col gap-8">
<div className="mb-2 border-b border-custom-border-100 pb-3">
<div className="pb-1 text-xl font-medium text-custom-text-100">
ID your instance easily
</div>
<div className="text-sm font-normal text-custom-text-300">
Change the name of your instance and instance admin e-mail addresses.
If you have a paid subscription, you will find your license key here.
</div>
</div>
{/* {instance && instanceAdmins ? (
<InstanceGeneralForm
instance={instance}
instanceAdmins={instanceAdmins}
/>
) : (
<Loader className="space-y-4">
<div className="grid grid-cols-2 gap-x-8 gap-y-4">
<Loader.Item height="50px" />
<Loader.Item height="50px" />
</div>
<Loader.Item height="50px" />
</Loader>
)} */}
</div>
);
};

View File

@ -0,0 +1 @@
export * from "./general-view";

View File

@ -0,0 +1,12 @@
import { useContext } from "react";
// mobx store
import { InstanceContext } from "lib/instance-provider";
// types
import { IInstanceStore } from "store/instance.store";
export const useInstance = (): IInstanceStore => {
const context = useContext(InstanceContext);
if (context === undefined)
throw new Error("useTheme must be used within ThemeProvider");
return context;
};

View File

@ -0,0 +1,12 @@
import { useContext } from "react";
// mobx store
import { ThemeContext } from "lib/theme-provider";
// types
import { IThemeStore } from "store/theme.store";
export const useAppTheme = (): IThemeStore => {
const context = useContext(ThemeContext);
if (context === undefined)
throw new Error("useTheme must be used within ThemeProvider");
return context;
};

View File

@ -0,0 +1,29 @@
"use client";
import { createContext } from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import type { ThemeProviderProps } from "next-themes/dist/types";
// mobx store
import { InstanceStore } from "store/instance.store";
let instanceStore = new InstanceStore();
export const InstanceContext = createContext<InstanceStore>(instanceStore);
const initializeStore = () => {
const _instanceStore = instanceStore ?? new InstanceStore();
if (typeof window === "undefined") return _instanceStore;
if (!instanceStore) instanceStore = _instanceStore;
return _instanceStore;
};
export function InstanceProvider({ children, ...props }: ThemeProviderProps) {
const store = initializeStore();
return (
<>
<InstanceContext.Provider value={store}>
<NextThemesProvider {...props}>{children}</NextThemesProvider>
</InstanceContext.Provider>
</>
);
}

View File

@ -0,0 +1,31 @@
"use client";
import * as React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import type { ThemeProviderProps } from "next-themes/dist/types";
import { createContext } from "react";
// mobx store
import { ThemeStore } from "store/theme.store";
let themeStore = new ThemeStore();
export const ThemeContext = createContext<ThemeStore>(themeStore);
const initializeStore = () => {
const _themeStore = themeStore ?? new ThemeStore();
if (typeof window === "undefined") return _themeStore;
if (!themeStore) themeStore = _themeStore;
return _themeStore;
};
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
const store = initializeStore();
return (
<>
<ThemeContext.Provider value={store}>
<NextThemesProvider {...props}>{children}</NextThemesProvider>
</ThemeContext.Provider>
</>
);
}

5
god-mode/next-env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

7
god-mode/next.config.js Normal file
View File

@ -0,0 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
};
module.exports = nextConfig;

30
god-mode/package.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "god-mode",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --port 3333",
"build": "next build",
"preview": "next build && next start",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@tailwindcss/typography": "^0.5.9",
"@types/node": "18.16.1",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"autoprefixer": "10.4.14",
"eslint": "8.39.0",
"eslint-config-next": "13.3.1",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"next": "^14.1.0",
"next-themes": "^0.2.1",
"postcss": "8.4.23",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss": "3.3.2",
"typescript": "5.0.4"
}
}

View File

@ -0,0 +1,8 @@
module.exports = {
plugins: {
"postcss-import": {},
"tailwindcss/nesting": {},
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

1
god-mode/public/next.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

After

Width:  |  Height:  |  Size: 629 B

View File

@ -0,0 +1,14 @@
// mobx
import { action, observable, makeObservable } from "mobx";
export interface IInstanceStore {}
export class InstanceStore implements IInstanceStore {
constructor() {
makeObservable(this, {
// observable
// action
// computed
});
}
}

View File

@ -0,0 +1,206 @@
// mobx
import { action, observable, makeObservable } from "mobx";
type TRgb = { r: number; g: number; b: number };
type TShades = {
10: TRgb;
20: TRgb;
30: TRgb;
40: TRgb;
50: TRgb;
60: TRgb;
70: TRgb;
80: TRgb;
90: TRgb;
100: TRgb;
200: TRgb;
300: TRgb;
400: TRgb;
500: TRgb;
600: TRgb;
700: TRgb;
800: TRgb;
900: TRgb;
};
export interface IThemeStore {
// observables
theme: string | undefined;
sidebarCollapsed: boolean | undefined;
// actions
toggleSidebar: (collapsed?: boolean) => void;
setTheme: (theme: any) => void;
}
export class ThemeStore implements IThemeStore {
// observables
sidebarCollapsed: boolean | undefined = undefined;
theme: string | undefined = undefined;
themePallette: any | undefined = undefined;
constructor() {
makeObservable(this, {
// observable
sidebarCollapsed: observable.ref,
theme: observable.ref,
// action
toggleSidebar: action,
setTheme: action,
// computed
});
}
/**
* Toggle the sidebar collapsed state
* @param collapsed
*/
toggleSidebar = (collapsed?: boolean) => {
if (collapsed === undefined) {
this.sidebarCollapsed = !this.sidebarCollapsed;
} else {
this.sidebarCollapsed = collapsed;
}
localStorage.setItem(
"god_mode_sidebar_collapsed",
this.sidebarCollapsed.toString()
);
};
/**
* Sets the user theme and applies it to the platform
* @param _theme
*/
setTheme = async (_theme: { theme: any }) => {
try {
const currentTheme: string = _theme?.theme?.theme?.toString();
// updating the local storage theme value
localStorage.setItem("theme", currentTheme);
// updating the mobx theme value
this.theme = currentTheme;
// applying the theme to platform if the selected theme is custom
if (currentTheme === "custom" && this.themePallette) {
this.applyTheme(
this.themePallette?.theme?.palette !== ",,,,"
? this.themePallette?.theme?.palette
: "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5",
this.themePallette?.theme?.darkPalette
);
} else this.unsetCustomCssVariables();
} catch (error) {
console.error("setting user theme error", error);
}
};
calculateShades = (hexValue: string): TShades => {
const shades: Partial<TShades> = {};
const { r, g, b } = this.hexToRgb(hexValue);
const convertHexToSpecificShade = (shade: number): TRgb => {
if (shade <= 100) {
const decimalValue = (100 - shade) / 100;
const newR = Math.floor(r + (255 - r) * decimalValue);
const newG = Math.floor(g + (255 - g) * decimalValue);
const newB = Math.floor(b + (255 - b) * decimalValue);
return {
r: newR,
g: newG,
b: newB,
};
} else {
const decimalValue = 1 - Math.ceil((shade - 100) / 100) / 10;
const newR = Math.ceil(r * decimalValue);
const newG = Math.ceil(g * decimalValue);
const newB = Math.ceil(b * decimalValue);
return {
r: newR,
g: newG,
b: newB,
};
}
};
for (let i = 10; i <= 900; i >= 100 ? (i += 100) : (i += 10))
shades[i as keyof TShades] = convertHexToSpecificShade(i);
return shades as TShades;
};
unsetCustomCssVariables = () => {
for (let i = 10; i <= 900; i >= 100 ? (i += 100) : (i += 10)) {
const dom = document.querySelector<HTMLElement>("[data-theme='custom']");
dom?.style.removeProperty(`--color-background-${i}`);
dom?.style.removeProperty(`--color-text-${i}`);
dom?.style.removeProperty(`--color-border-${i}`);
dom?.style.removeProperty(`--color-primary-${i}`);
dom?.style.removeProperty(`--color-sidebar-background-${i}`);
dom?.style.removeProperty(`--color-sidebar-text-${i}`);
dom?.style.removeProperty(`--color-sidebar-border-${i}`);
dom?.style.removeProperty("--color-scheme");
}
};
applyTheme = (palette: string, isDarkPalette: boolean) => {
if (!palette) return;
const dom = document?.querySelector<HTMLElement>("[data-theme='custom']");
// palette: [bg, text, primary, sidebarBg, sidebarText]
const values: string[] = palette.split(",");
values.push(isDarkPalette ? "dark" : "light");
const bgShades = this.calculateShades(values[0]);
const textShades = this.calculateShades(values[1]);
const primaryShades = this.calculateShades(values[2]);
const sidebarBackgroundShades = this.calculateShades(values[3]);
const sidebarTextShades = this.calculateShades(values[4]);
for (let i = 10; i <= 900; i >= 100 ? (i += 100) : (i += 10)) {
const shade = i as keyof TShades;
const bgRgbValues = `${bgShades[shade].r}, ${bgShades[shade].g}, ${bgShades[shade].b}`;
const textRgbValues = `${textShades[shade].r}, ${textShades[shade].g}, ${textShades[shade].b}`;
const primaryRgbValues = `${primaryShades[shade].r}, ${primaryShades[shade].g}, ${primaryShades[shade].b}`;
const sidebarBackgroundRgbValues = `${sidebarBackgroundShades[shade].r}, ${sidebarBackgroundShades[shade].g}, ${sidebarBackgroundShades[shade].b}`;
const sidebarTextRgbValues = `${sidebarTextShades[shade].r}, ${sidebarTextShades[shade].g}, ${sidebarTextShades[shade].b}`;
dom?.style.setProperty(`--color-background-${shade}`, bgRgbValues);
dom?.style.setProperty(`--color-text-${shade}`, textRgbValues);
dom?.style.setProperty(`--color-primary-${shade}`, primaryRgbValues);
dom?.style.setProperty(
`--color-sidebar-background-${shade}`,
sidebarBackgroundRgbValues
);
dom?.style.setProperty(
`--color-sidebar-text-${shade}`,
sidebarTextRgbValues
);
if (i >= 100 && i <= 400) {
const borderShade =
i === 100 ? 70 : i === 200 ? 80 : i === 300 ? 90 : 100;
dom?.style.setProperty(
`--color-border-${shade}`,
`${bgShades[borderShade].r}, ${bgShades[borderShade].g}, ${bgShades[borderShade].b}`
);
dom?.style.setProperty(
`--color-sidebar-border-${shade}`,
`${sidebarBackgroundShades[borderShade].r}, ${sidebarBackgroundShades[borderShade].g}, ${sidebarBackgroundShades[borderShade].b}`
);
}
}
dom?.style.setProperty("--color-scheme", values[5]);
};
hexToRgb = (hex: string): TRgb => {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return { r, g, b };
};
}

View File

@ -0,0 +1,5 @@
const sharedConfig = require("tailwind-config-custom/tailwind.config.js");
module.exports = {
presets: [sharedConfig],
};

22
god-mode/tsconfig.json Normal file
View File

@ -0,0 +1,22 @@
{
"extends": "tsconfig/nextjs.json",
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
],
"compilerOptions": {
"baseUrl": ".",
"jsx": "preserve",
"esModuleInterop": true,
"plugins": [
{
"name": "next"
}
]
}
}

2701
god-mode/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +0,0 @@
export * from "./layout";
export * from "./sidebar";
export * from "./header";

View File

@ -5,8 +5,8 @@ import { useApplication } from "hooks/store";
// layouts
import { AdminAuthWrapper, UserAuthWrapper } from "layouts/auth-layout";
// components
import { InstanceAdminSidebar } from "./sidebar";
import { InstanceAdminHeader } from "./header";
import { InstanceAdminSidebar } from "../../../god-mode/app/sidebar";
import { InstanceAdminHeader } from "../../../god-mode/app/header";
import { InstanceSetupView } from "components/instance";
export interface IInstanceAdminLayout {

View File

@ -1,29 +0,0 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
// hooks
import { useApplication } from "hooks/store";
// components
import { InstanceAdminSidebarMenu, InstanceHelpSection, InstanceSidebarDropdown } from "components/instance";
export interface IInstanceAdminSidebar {}
export const InstanceAdminSidebar: FC<IInstanceAdminSidebar> = observer(() => {
// store
const {
theme: { sidebarCollapsed },
} = useApplication();
return (
<div
className={`fixed inset-y-0 z-20 flex h-full flex-shrink-0 flex-grow-0 flex-col border-r border-custom-sidebar-border-200 bg-custom-sidebar-background-100 duration-300 md:relative ${
sidebarCollapsed ? "" : "md:w-[280px]"
} ${sidebarCollapsed ? "left-0" : "-left-full md:left-0"}`}
>
<div className="flex h-full w-full flex-1 flex-col">
<InstanceSidebarDropdown />
<InstanceAdminSidebarMenu />
<InstanceHelpSection />
</div>
</div>
);
});

View File

@ -1,6 +1,6 @@
{
"extends": "tsconfig/nextjs.json",
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "../god-mode/app/sidebar.tsx", "../god-mode/app/header.tsx"],
"exclude": ["node_modules"],
"compilerOptions": {
"baseUrl": ".",