mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: god mode changes
This commit is contained in:
parent
c9d2ea36b8
commit
8445cef3b4
72
god-mode/app/ai/page.tsx
Normal file
72
god-mode/app/ai/page.tsx
Normal 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;
|
174
god-mode/app/authorization/page.tsx
Normal file
174
god-mode/app/authorization/page.tsx
Normal 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;
|
56
god-mode/app/email/page.tsx
Normal file
56
god-mode/app/email/page.tsx
Normal 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
6
god-mode/app/globals.css
Normal 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;
|
@ -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>
|
50
god-mode/app/image/page.tsx
Normal file
50
god-mode/app/image/page.tsx
Normal 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
51
god-mode/app/layout.tsx
Normal 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
9
god-mode/app/page.tsx
Normal 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
28
god-mode/app/sidebar.tsx
Normal 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>
|
||||
);
|
||||
};
|
142
god-mode/components/sidebar/help-section.tsx
Normal file
142
god-mode/components/sidebar/help-section.tsx
Normal 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>
|
||||
);
|
||||
};
|
3
god-mode/components/sidebar/index.ts
Normal file
3
god-mode/components/sidebar/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./help-section";
|
||||
export * from "./sidebar-menu";
|
||||
export * from "./sidebar-dropdown";
|
158
god-mode/components/sidebar/sidebar-dropdown.tsx
Normal file
158
god-mode/components/sidebar/sidebar-dropdown.tsx
Normal 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>
|
||||
);
|
||||
});
|
104
god-mode/components/sidebar/sidebar-menu.tsx
Normal file
104
god-mode/components/sidebar/sidebar-menu.tsx
Normal 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>
|
||||
);
|
||||
});
|
30
god-mode/components/views/general-view.tsx
Normal file
30
god-mode/components/views/general-view.tsx
Normal 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>
|
||||
);
|
||||
};
|
1
god-mode/components/views/index.ts
Normal file
1
god-mode/components/views/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./general-view";
|
12
god-mode/hooks/useInstance.tsx
Normal file
12
god-mode/hooks/useInstance.tsx
Normal 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;
|
||||
};
|
12
god-mode/hooks/useTheme.tsx
Normal file
12
god-mode/hooks/useTheme.tsx
Normal 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;
|
||||
};
|
29
god-mode/lib/instance-provider.tsx
Normal file
29
god-mode/lib/instance-provider.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
31
god-mode/lib/theme-provider.tsx
Normal file
31
god-mode/lib/theme-provider.tsx
Normal 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
5
god-mode/next-env.d.ts
vendored
Normal 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
7
god-mode/next.config.js
Normal 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
30
god-mode/package.json
Normal 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"
|
||||
}
|
||||
}
|
8
god-mode/postcss.config.js
Normal file
8
god-mode/postcss.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
"postcss-import": {},
|
||||
"tailwindcss/nesting": {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
BIN
god-mode/public/blog-post-1.jpg
Normal file
BIN
god-mode/public/blog-post-1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
BIN
god-mode/public/blog-post-2.jpg
Normal file
BIN
god-mode/public/blog-post-2.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
BIN
god-mode/public/blog-post-3.jpg
Normal file
BIN
god-mode/public/blog-post-3.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 69 KiB |
BIN
god-mode/public/blog-post-4.jpg
Normal file
BIN
god-mode/public/blog-post-4.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 88 KiB |
1
god-mode/public/next.svg
Normal file
1
god-mode/public/next.svg
Normal 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 |
1
god-mode/public/vercel.svg
Normal file
1
god-mode/public/vercel.svg
Normal 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 |
14
god-mode/store/instance.store.ts
Normal file
14
god-mode/store/instance.store.ts
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
206
god-mode/store/theme.store.ts
Normal file
206
god-mode/store/theme.store.ts
Normal 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 };
|
||||
};
|
||||
}
|
5
god-mode/tailwind.config.js
Normal file
5
god-mode/tailwind.config.js
Normal 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
22
god-mode/tsconfig.json
Normal 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
2701
god-mode/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,3 +0,0 @@
|
||||
export * from "./layout";
|
||||
export * from "./sidebar";
|
||||
export * from "./header";
|
@ -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 {
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
@ -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": ".",
|
||||
|
Loading…
Reference in New Issue
Block a user