From 5a6a754a70c036ac5a52c04b3e1796ce409dc0a5 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Thu, 15 Feb 2024 12:21:19 +0530 Subject: [PATCH] chore: god mode settings. --- god-mode/app/ai/page.tsx | 19 +- god-mode/app/authorization/page.tsx | 94 +++-- god-mode/app/email/page.tsx | 18 +- god-mode/app/globals.css | 336 ++++++++++++++++++ god-mode/app/header.tsx | 2 +- god-mode/app/image/page.tsx | 19 +- god-mode/app/layout.tsx | 49 +-- god-mode/app/page.tsx | 32 ++ god-mode/app/sidebar.tsx | 7 +- god-mode/components/forms/ai-form.tsx | 151 ++++++++ god-mode/components/forms/email-form.tsx | 266 ++++++++++++++ god-mode/components/forms/general-form.tsx | 130 +++++++ .../components/forms/github-config-form.tsx | 187 ++++++++++ .../components/forms/google-config-form.tsx | 136 +++++++ .../components/forms/image-config-form.tsx | 119 +++++++ god-mode/components/forms/index.ts | 6 + god-mode/components/sidebar/help-section.tsx | 4 +- .../components/sidebar/sidebar-dropdown.tsx | 124 +++---- god-mode/components/sidebar/sidebar-menu.tsx | 2 +- god-mode/components/toast-alert/index.tsx | 61 ++++ god-mode/components/views/general-view.tsx | 18 +- .../{useInstance.tsx => use-instance.tsx} | 6 +- .../hooks/{useTheme.tsx => use-theme.tsx} | 2 + god-mode/hooks/use-toast.tsx | 9 + god-mode/hooks/use-user.tsx | 14 + god-mode/lib/toast-provider.tsx | 99 ++++++ god-mode/lib/user-provider.tsx | 29 ++ god-mode/package.json | 3 + god-mode/services/api.service.ts | 94 +++++ god-mode/services/auth.service.ts | 148 ++++++++ god-mode/services/instance.service.ts | 53 +++ god-mode/services/user.service.ts | 37 ++ god-mode/store/instance.store.ts | 143 +++++++- god-mode/store/user.store.ts | 100 ++++++ .../tailwind-config-custom/tailwind.config.js | 1 + yarn.lock | 18 +- 36 files changed, 2320 insertions(+), 216 deletions(-) create mode 100644 god-mode/components/forms/ai-form.tsx create mode 100644 god-mode/components/forms/email-form.tsx create mode 100644 god-mode/components/forms/general-form.tsx create mode 100644 god-mode/components/forms/github-config-form.tsx create mode 100644 god-mode/components/forms/google-config-form.tsx create mode 100644 god-mode/components/forms/image-config-form.tsx create mode 100644 god-mode/components/forms/index.ts create mode 100644 god-mode/components/toast-alert/index.tsx rename god-mode/hooks/{useInstance.tsx => use-instance.tsx} (64%) rename god-mode/hooks/{useTheme.tsx => use-theme.tsx} (92%) create mode 100644 god-mode/hooks/use-toast.tsx create mode 100644 god-mode/hooks/use-user.tsx create mode 100644 god-mode/lib/toast-provider.tsx create mode 100644 god-mode/lib/user-provider.tsx create mode 100644 god-mode/services/api.service.ts create mode 100644 god-mode/services/auth.service.ts create mode 100644 god-mode/services/instance.service.ts create mode 100644 god-mode/services/user.service.ts create mode 100644 god-mode/store/user.store.ts diff --git a/god-mode/app/ai/page.tsx b/god-mode/app/ai/page.tsx index 87df2da50..250cc717a 100644 --- a/god-mode/app/ai/page.tsx +++ b/god-mode/app/ai/page.tsx @@ -1,28 +1,21 @@ "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"; +import useInstance from "hooks/use-instance"; // ui import { Loader } from "@plane/ui"; // icons import { Lightbulb } from "lucide-react"; // components -// import { InstanceAIForm } from "components/instance"; +import { InstanceAIForm } from "components/forms"; const InstanceAIPage = observer(() => { // store - // const { - // instance: { fetchInstanceConfigurations, formattedConfig }, - // } = useApplication(); + const { fetchInstanceConfigurations, formattedConfig } = useInstance(); - // useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); + useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); return (
@@ -35,7 +28,7 @@ const InstanceAIPage = observer(() => { for all your workspaces.
- {/* {formattedConfig ? ( + {formattedConfig ? ( <>
@@ -64,7 +57,7 @@ const InstanceAIPage = observer(() => {
- )} */} + )}
); }); diff --git a/god-mode/app/authorization/page.tsx b/god-mode/app/authorization/page.tsx index 7d5a770a4..d746b6b82 100644 --- a/god-mode/app/authorization/page.tsx +++ b/god-mode/app/authorization/page.tsx @@ -1,75 +1,67 @@ "use client"; -import { ReactElement, useState } from "react"; + +import { 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"; +import useInstance from "hooks/use-instance"; // hooks -// import useToast from "hooks/use-toast"; +import useToast from "hooks/use-toast"; // ui import { Loader, ToggleSwitch } from "@plane/ui"; // components -// import { -// InstanceGithubConfigForm, -// InstanceGoogleConfigForm, -// } from "components/instance"; +import { InstanceGithubConfigForm, InstanceGoogleConfigForm } from "components/forms"; const InstanceAuthorizationPage = observer(() => { // store - // const { - // instance: { - // fetchInstanceConfigurations, - // formattedConfig, - // updateInstanceConfigurations, - // }, - // } = useApplication(); + const { + fetchInstanceConfigurations, + formattedConfig, + updateInstanceConfigurations, + } = useInstance(); - // useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); + useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); // toast - // const { setToastAlert } = useToast(); + const { setToastAlert } = useToast(); // state const [isSubmitting, setIsSubmitting] = useState(false); - // const enableSignup = formattedConfig?.ENABLE_SIGNUP ?? "0"; - // const enableMagicLogin = formattedConfig?.ENABLE_MAGIC_LINK_LOGIN ?? "0"; + 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 updateConfig = async ( + key: "ENABLE_SIGNUP" | "ENABLE_MAGIC_LINK_LOGIN" | "ENABLE_EMAIL_PASSWORD", + value: string + ) => { + setIsSubmitting(true); - // const payload = { - // [key]: value, - // }; + 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); - // }); - // }; + 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 (
@@ -82,7 +74,7 @@ const InstanceAuthorizationPage = observer(() => { and GitHub accounts, and below are the settings.
- {/* {formattedConfig ? ( + {formattedConfig ? ( <>
@@ -166,7 +158,7 @@ const InstanceAuthorizationPage = observer(() => {
- )} */} + )}
); }); diff --git a/god-mode/app/email/page.tsx b/god-mode/app/email/page.tsx index 6e98b8cc2..00d1eabe1 100644 --- a/god-mode/app/email/page.tsx +++ b/god-mode/app/email/page.tsx @@ -1,25 +1,19 @@ "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"; +import useInstance from "hooks/use-instance"; // ui import { Loader } from "@plane/ui"; // components -// import { InstanceEmailForm } from "components/instance"; +import { InstanceEmailForm } from "components/forms"; const InstanceEmailPage = observer(() => { // store - // const { - // instance: { fetchInstanceConfigurations, formattedConfig }, - // } = useApplication(); + const { fetchInstanceConfigurations, formattedConfig } = useInstance(); - // useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); + useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); return (
@@ -38,7 +32,7 @@ const InstanceEmailPage = observer(() => {
- {/* {formattedConfig ? ( + {formattedConfig ? ( ) : ( @@ -48,7 +42,7 @@ const InstanceEmailPage = observer(() => { - )} */} + )} ); }); diff --git a/god-mode/app/globals.css b/god-mode/app/globals.css index e493f0abc..25a454271 100644 --- a/god-mode/app/globals.css +++ b/god-mode/app/globals.css @@ -4,3 +4,339 @@ @tailwind base; @tailwind components; @tailwind utilities; + +@layer components { + .text-1\.5xl { + font-size: 1.375rem; + line-height: 1.875rem; + } + + .text-2\.5xl { + font-size: 1.75rem; + line-height: 2.25rem; + } +} + +@layer base { + html { + font-family: "Inter", sans-serif; + } + + :root { + color-scheme: light !important; + + --color-primary-10: 236, 241, 255; + --color-primary-20: 217, 228, 255; + --color-primary-30: 197, 214, 255; + --color-primary-40: 178, 200, 255; + --color-primary-50: 159, 187, 255; + --color-primary-60: 140, 173, 255; + --color-primary-70: 121, 159, 255; + --color-primary-80: 101, 145, 255; + --color-primary-90: 82, 132, 255; + --color-primary-100: 63, 118, 255; + --color-primary-200: 57, 106, 230; + --color-primary-300: 50, 94, 204; + --color-primary-400: 44, 83, 179; + --color-primary-500: 38, 71, 153; + --color-primary-600: 32, 59, 128; + --color-primary-700: 25, 47, 102; + --color-primary-800: 19, 35, 76; + --color-primary-900: 13, 24, 51; + + --color-background-100: 255, 255, 255; /* primary bg */ + --color-background-90: 250, 250, 250; /* secondary bg */ + --color-background-80: 245, 245, 245; /* tertiary bg */ + + --color-text-100: 23, 23, 23; /* primary text */ + --color-text-200: 58, 58, 58; /* secondary text */ + --color-text-300: 82, 82, 82; /* tertiary text */ + --color-text-400: 163, 163, 163; /* placeholder text */ + + --color-scrollbar: 163, 163, 163; /* scrollbar thumb */ + + --color-border-100: 245, 245, 245; /* subtle border= 1 */ + --color-border-200: 229, 229, 229; /* subtle border- 2 */ + --color-border-300: 212, 212, 212; /* strong border- 1 */ + --color-border-400: 185, 185, 185; /* strong border- 2 */ + + --color-shadow-2xs: 0px 0px 1px 0px rgba(23, 23, 23, 0.06), 0px 1px 2px 0px rgba(23, 23, 23, 0.06), + 0px 1px 2px 0px rgba(23, 23, 23, 0.14); + --color-shadow-xs: 0px 1px 2px 0px rgba(0, 0, 0, 0.16), 0px 2px 4px 0px rgba(16, 24, 40, 0.12), + 0px 1px 8px -1px rgba(16, 24, 40, 0.1); + --color-shadow-sm: 0px 1px 4px 0px rgba(0, 0, 0, 0.01), 0px 4px 8px 0px rgba(0, 0, 0, 0.02), + 0px 1px 12px 0px rgba(0, 0, 0, 0.12); + --color-shadow-rg: 0px 3px 6px 0px rgba(0, 0, 0, 0.1), 0px 4px 4px 0px rgba(16, 24, 40, 0.08), + 0px 1px 12px 0px rgba(16, 24, 40, 0.04); + --color-shadow-md: 0px 4px 8px 0px rgba(0, 0, 0, 0.12), 0px 6px 12px 0px rgba(16, 24, 40, 0.12), + 0px 1px 16px 0px rgba(16, 24, 40, 0.12); + --color-shadow-lg: 0px 6px 12px 0px rgba(0, 0, 0, 0.12), 0px 8px 16px 0px rgba(0, 0, 0, 0.12), + 0px 1px 24px 0px rgba(16, 24, 40, 0.12); + --color-shadow-xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.16), 0px 0px 24px 0px rgba(16, 24, 40, 0.16), + 0px 0px 52px 0px rgba(16, 24, 40, 0.16); + --color-shadow-2xl: 0px 8px 16px 0px rgba(0, 0, 0, 0.12), 0px 12px 24px 0px rgba(16, 24, 40, 0.12), + 0px 1px 32px 0px rgba(16, 24, 40, 0.12); + --color-shadow-3xl: 0px 12px 24px 0px rgba(0, 0, 0, 0.12), 0px 16px 32px 0px rgba(0, 0, 0, 0.12), + 0px 1px 48px 0px rgba(16, 24, 40, 0.12); + --color-shadow-4xl: 0px 8px 40px 0px rgba(0, 0, 61, 0.05), 0px 12px 32px -16px rgba(0, 0, 0, 0.05); + + --color-sidebar-background-100: var(--color-background-100); /* primary sidebar bg */ + --color-sidebar-background-90: var(--color-background-90); /* secondary sidebar bg */ + --color-sidebar-background-80: var(--color-background-80); /* tertiary sidebar bg */ + + --color-sidebar-text-100: var(--color-text-100); /* primary sidebar text */ + --color-sidebar-text-200: var(--color-text-200); /* secondary sidebar text */ + --color-sidebar-text-300: var(--color-text-300); /* tertiary sidebar text */ + --color-sidebar-text-400: var(--color-text-400); /* sidebar placeholder text */ + + --color-sidebar-border-100: var(--color-border-100); /* subtle sidebar border= 1 */ + --color-sidebar-border-200: var(--color-border-100); /* subtle sidebar border- 2 */ + --color-sidebar-border-300: var(--color-border-100); /* strong sidebar border- 1 */ + --color-sidebar-border-400: var(--color-border-100); /* strong sidebar border- 2 */ + + --color-sidebar-shadow-2xs: var(--color-shadow-2xs); + --color-sidebar-shadow-xs: var(--color-shadow-xs); + --color-sidebar-shadow-sm: var(--color-shadow-sm); + --color-sidebar-shadow-rg: var(--color-shadow-rg); + --color-sidebar-shadow-md: var(--color-shadow-md); + --color-sidebar-shadow-lg: var(--color-shadow-lg); + --color-sidebar-shadow-xl: var(--color-shadow-xl); + --color-sidebar-shadow-2xl: var(--color-shadow-2xl); + --color-sidebar-shadow-3xl: var(--color-shadow-3xl); + --color-sidebar-shadow-4xl: var(--color-shadow-4xl); + } + + [data-theme="light"], + [data-theme="light-contrast"] { + color-scheme: light !important; + + --color-background-100: 255, 255, 255; /* primary bg */ + --color-background-90: 250, 250, 250; /* secondary bg */ + --color-background-80: 245, 245, 245; /* tertiary bg */ + } + + [data-theme="light"] { + --color-text-100: 23, 23, 23; /* primary text */ + --color-text-200: 58, 58, 58; /* secondary text */ + --color-text-300: 82, 82, 82; /* tertiary text */ + --color-text-400: 163, 163, 163; /* placeholder text */ + + --color-scrollbar: 163, 163, 163; /* scrollbar thumb */ + + --color-border-100: 245, 245, 245; /* subtle border= 1 */ + --color-border-200: 229, 229, 229; /* subtle border- 2 */ + --color-border-300: 212, 212, 212; /* strong border- 1 */ + --color-border-400: 185, 185, 185; /* strong border- 2 */ + + /* onboarding colors */ + --gradient-onboarding-100: linear-gradient(106deg, #f2f6ff 29.8%, #e1eaff 99.34%); + --gradient-onboarding-200: linear-gradient(129deg, rgba(255, 255, 255, 0) -22.23%, rgba(255, 255, 255, 0.8) 62.98%); + --gradient-onboarding-300: linear-gradient(164deg, #fff 4.25%, rgba(255, 255, 255, 0.06) 93.5%); + --gradient-onboarding-400: linear-gradient(129deg, rgba(255, 255, 255, 0) -22.23%, rgba(255, 255, 255, 0.8) 62.98%); + + --color-onboarding-text-100: 23, 23, 23; + --color-onboarding-text-200: 58, 58, 58; + --color-onboarding-text-300: 82, 82, 82; + --color-onboarding-text-400: 163, 163, 163; + + --color-onboarding-background-100: 236, 241, 255; + --color-onboarding-background-200: 255, 255, 255; + --color-onboarding-background-300: 236, 241, 255; + --color-onboarding-background-400: 177, 206, 250; + + --color-onboarding-border-100: 229, 229, 229; + --color-onboarding-border-200: 217, 228, 255; + --color-onboarding-border-300: 229, 229, 229, 0.5; + + --color-onboarding-shadow-sm: 0px 4px 20px 0px rgba(126, 139, 171, 0.1); + } + + [data-theme="light-contrast"] { + --color-text-100: 11, 11, 11; /* primary text */ + --color-text-200: 38, 38, 38; /* secondary text */ + --color-text-300: 58, 58, 58; /* tertiary text */ + --color-text-400: 115, 115, 115; /* placeholder text */ + + --color-scrollbar: 115, 115, 115; /* scrollbar thumb */ + + --color-border-100: 34, 34, 34; /* subtle border= 1 */ + --color-border-200: 38, 38, 38; /* subtle border- 2 */ + --color-border-300: 46, 46, 46; /* strong border- 1 */ + --color-border-400: 58, 58, 58; /* strong border- 2 */ + } + + [data-theme="dark"], + [data-theme="dark-contrast"] { + color-scheme: dark !important; + + --color-background-100: 7, 7, 7; /* primary bg */ + --color-background-90: 11, 11, 11; /* secondary bg */ + --color-background-80: 23, 23, 23; /* tertiary bg */ + + --color-shadow-2xs: 0px 0px 1px 0px rgba(0, 0, 0, 0.15), 0px 1px 3px 0px rgba(0, 0, 0, 0.5); + --color-shadow-xs: 0px 0px 2px 0px rgba(0, 0, 0, 0.2), 0px 2px 4px 0px rgba(0, 0, 0, 0.5); + --color-shadow-sm: 0px 0px 4px 0px rgba(0, 0, 0, 0.2), 0px 2px 6px 0px rgba(0, 0, 0, 0.5); + --color-shadow-rg: 0px 0px 6px 0px rgba(0, 0, 0, 0.2), 0px 4px 6px 0px rgba(0, 0, 0, 0.5); + --color-shadow-md: 0px 2px 8px 0px rgba(0, 0, 0, 0.2), 0px 4px 8px 0px rgba(0, 0, 0, 0.5); + --color-shadow-lg: 0px 4px 12px 0px rgba(0, 0, 0, 0.25), 0px 4px 10px 0px rgba(0, 0, 0, 0.55); + --color-shadow-xl: 0px 0px 14px 0px rgba(0, 0, 0, 0.25), 0px 6px 10px 0px rgba(0, 0, 0, 0.55); + --color-shadow-2xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.25), 0px 8px 12px 0px rgba(0, 0, 0, 0.6); + --color-shadow-3xl: 0px 4px 24px 0px rgba(0, 0, 0, 0.3), 0px 12px 40px 0px rgba(0, 0, 0, 0.65); + } + + [data-theme="dark"] { + --color-text-100: 229, 229, 229; /* primary text */ + --color-text-200: 163, 163, 163; /* secondary text */ + --color-text-300: 115, 115, 115; /* tertiary text */ + --color-text-400: 82, 82, 82; /* placeholder text */ + + --color-scrollbar: 82, 82, 82; /* scrollbar thumb */ + + --color-border-100: 34, 34, 34; /* subtle border= 1 */ + --color-border-200: 38, 38, 38; /* subtle border- 2 */ + --color-border-300: 46, 46, 46; /* strong border- 1 */ + --color-border-400: 58, 58, 58; /* strong border- 2 */ + + /* onboarding colors */ + --gradient-onboarding-100: linear-gradient(106deg, #18191b 25.17%, #18191b 99.34%); + --gradient-onboarding-200: linear-gradient(129deg, rgba(47, 49, 53, 0.8) -22.23%, rgba(33, 34, 37, 0.8) 62.98%); + --gradient-onboarding-300: linear-gradient(167deg, rgba(47, 49, 53, 0.45) 19.22%, #212225 98.48%); + + --color-onboarding-text-100: 237, 238, 240; + --color-onboarding-text-200: 176, 180, 187; + --color-onboarding-text-300: 118, 123, 132; + --color-onboarding-text-400: 105, 110, 119; + + --color-onboarding-background-100: 54, 58, 64; + --color-onboarding-background-200: 40, 42, 45; + --color-onboarding-background-300: 40, 42, 45; + --color-onboarding-background-400: 67, 72, 79; + + --color-onboarding-border-100: 54, 58, 64; + --color-onboarding-border-200: 54, 58, 64; + --color-onboarding-border-300: 34, 35, 38, 0.5; + + --color-onboarding-shadow-sm: 0px 4px 20px 0px rgba(39, 44, 56, 0.1); + } + + [data-theme="dark-contrast"] { + --color-text-100: 250, 250, 250; /* primary text */ + --color-text-200: 241, 241, 241; /* secondary text */ + --color-text-300: 212, 212, 212; /* tertiary text */ + --color-text-400: 115, 115, 115; /* placeholder text */ + + --color-scrollbar: 115, 115, 115; /* scrollbar thumb */ + + --color-border-100: 245, 245, 245; /* subtle border= 1 */ + --color-border-200: 229, 229, 229; /* subtle border- 2 */ + --color-border-300: 212, 212, 212; /* strong border- 1 */ + --color-border-400: 185, 185, 185; /* strong border- 2 */ + } + + [data-theme="light"], + [data-theme="dark"], + [data-theme="light-contrast"], + [data-theme="dark-contrast"] { + --color-primary-10: 236, 241, 255; + --color-primary-20: 217, 228, 255; + --color-primary-30: 197, 214, 255; + --color-primary-40: 178, 200, 255; + --color-primary-50: 159, 187, 255; + --color-primary-60: 140, 173, 255; + --color-primary-70: 121, 159, 255; + --color-primary-80: 101, 145, 255; + --color-primary-90: 82, 132, 255; + --color-primary-100: 63, 118, 255; + --color-primary-200: 57, 106, 230; + --color-primary-300: 50, 94, 204; + --color-primary-400: 44, 83, 179; + --color-primary-500: 38, 71, 153; + --color-primary-600: 32, 59, 128; + --color-primary-700: 25, 47, 102; + --color-primary-800: 19, 35, 76; + --color-primary-900: 13, 24, 51; + + --color-sidebar-background-100: var(--color-background-100); /* primary sidebar bg */ + --color-sidebar-background-90: var(--color-background-90); /* secondary sidebar bg */ + --color-sidebar-background-80: var(--color-background-80); /* tertiary sidebar bg */ + + --color-sidebar-text-100: var(--color-text-100); /* primary sidebar text */ + --color-sidebar-text-200: var(--color-text-200); /* secondary sidebar text */ + --color-sidebar-text-300: var(--color-text-300); /* tertiary sidebar text */ + --color-sidebar-text-400: var(--color-text-400); /* sidebar placeholder text */ + + --color-sidebar-border-100: var(--color-border-100); /* subtle sidebar border= 1 */ + --color-sidebar-border-200: var(--color-border-200); /* subtle sidebar border- 2 */ + --color-sidebar-border-300: var(--color-border-300); /* strong sidebar border- 1 */ + --color-sidebar-border-400: var(--color-border-400); /* strong sidebar border- 2 */ + } +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + font-variant-ligatures: none; + -webkit-font-variant-ligatures: none; + text-rendering: optimizeLegibility; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; +} + +body { + color: rgba(var(--color-text-100)); +} + +/* scrollbar style */ +::-webkit-scrollbar { + display: none; +} + +.horizontal-scroll-enable { + overflow-x: scroll; +} + +.horizontal-scroll-enable::-webkit-scrollbar { + display: block; + height: 7px; + width: 0; +} + +.horizontal-scroll-enable::-webkit-scrollbar-track { + height: 7px; + background-color: rgba(var(--color-background-100)); +} + +.horizontal-scroll-enable::-webkit-scrollbar-thumb { + border-radius: 5px; + background-color: rgba(var(--color-scrollbar)); +} + +.vertical-scroll-enable::-webkit-scrollbar { + display: block; + width: 5px; +} + +.vertical-scroll-enable::-webkit-scrollbar-track { + width: 5px; +} + +.vertical-scroll-enable::-webkit-scrollbar-thumb { + border-radius: 5px; + background-color: rgba(var(--color-background-90)); +} +/* end scrollbar style */ + +/* progress bar */ +.progress-bar { + fill: currentColor; + color: rgba(var(--color-sidebar-background-100)); +} + +::-webkit-input-placeholder, +::placeholder, +:-ms-input-placeholder { + color: rgb(var(--color-text-400)); +} diff --git a/god-mode/app/header.tsx b/god-mode/app/header.tsx index 565b2b1bb..f8a5b5386 100644 --- a/god-mode/app/header.tsx +++ b/god-mode/app/header.tsx @@ -33,7 +33,7 @@ export const InstanceHeader: FC = observer(() => { const title = getHeaderTitle(); return ( -
+
{title && (
diff --git a/god-mode/app/image/page.tsx b/god-mode/app/image/page.tsx index 17cbd97a9..c253a73b5 100644 --- a/god-mode/app/image/page.tsx +++ b/god-mode/app/image/page.tsx @@ -1,26 +1,19 @@ "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"; +import useInstance from "hooks/use-instance"; // ui import { Loader } from "@plane/ui"; // components -// import { InstanceImageConfigForm } from "components/instance"; +import { InstanceImageConfigForm } from "components/forms"; const InstanceImagePage = observer(() => { // store - // const { - // instance: { fetchInstanceConfigurations, formattedConfig }, - // } = useApplication(); + const { fetchInstanceConfigurations, formattedConfig } = useInstance(); - // useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); + useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); return (
@@ -32,7 +25,7 @@ const InstanceImagePage = observer(() => { Let your users search and choose images from third-party libraries
- {/* {formattedConfig ? ( + {formattedConfig ? ( ) : ( @@ -42,7 +35,7 @@ const InstanceImagePage = observer(() => {
- )} */} + )}
); }); diff --git a/god-mode/app/layout.tsx b/god-mode/app/layout.tsx index aeea9e42d..d67a188a7 100644 --- a/god-mode/app/layout.tsx +++ b/god-mode/app/layout.tsx @@ -1,8 +1,11 @@ -import "./globals.css"; +// lib import { ThemeProvider } from "lib/theme-provider"; +import { ToastContextProvider } from "lib/toast-provider"; // components import { InstanceSidebar } from "./sidebar"; import { InstanceHeader } from "./header"; +// styles +import "./globals.css"; export const metadata = { title: "God Mode", @@ -13,23 +16,19 @@ 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); - +export const RootLayout = async ({ children }: RootLayoutProps) => { + const isUserInstanceAdmin = true; return ( {/* */} - {/* {response?.is_instance_admin ? ( - + {/* {isUserInstanceAdmin || true ? ( */} + +
@@ -41,24 +40,14 @@ export default async function RootLayout({ children }: RootLayoutProps) {
- - ) : ( + + + {/* ) : (
Login
)} */} - -
- -
- -
-
- {children} -
-
-
-
-
); -} +}; + +export default RootLayout; diff --git a/god-mode/app/page.tsx b/god-mode/app/page.tsx index d7ffbfa62..b3e6098a9 100644 --- a/god-mode/app/page.tsx +++ b/god-mode/app/page.tsx @@ -1,6 +1,38 @@ +"use client"; + +import useSWR from "swr"; +import useSWRImmutable from "swr/immutable"; +// hooks +import useUser from "hooks/use-user"; +import useInstance from "hooks/use-instance"; +// components import { GeneralView } from "components/views"; export default function Home() { + const { + // isUserInstanceAdmin, + fetchCurrentUser, + fetchCurrentUserInstanceAdminStatus, + } = useUser(); + const { fetchInstanceInfo, fetchInstanceAdmins } = useInstance(); + + // fetching user information + useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), { + shouldRetryOnError: false, + }); + // fetching current user instance admin status + useSWRImmutable( + "CURRENT_USER_INSTANCE_ADMIN_STATUS", + () => fetchCurrentUserInstanceAdminStatus(), + { + shouldRetryOnError: false, + } + ); + // fetching instance information + useSWR("INSTANCE_INFO", () => fetchInstanceInfo()); + // fetching instance admins + useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins()); + return (
diff --git a/god-mode/app/sidebar.tsx b/god-mode/app/sidebar.tsx index 74d54bd3b..bbf792f95 100644 --- a/god-mode/app/sidebar.tsx +++ b/god-mode/app/sidebar.tsx @@ -1,14 +1,15 @@ "use client"; import { FC } from "react"; +import { observer } from "mobx-react-lite"; // hooks -import { useAppTheme } from "hooks/useTheme"; +import useAppTheme from "hooks/use-theme"; // components import { HelpSection, SidebarMenu, SidebarDropdown } from "components/sidebar"; export interface IInstanceSidebar {} -export const InstanceSidebar: FC = () => { +export const InstanceSidebar: FC = observer(() => { // store const { sidebarCollapsed } = useAppTheme(); @@ -25,4 +26,4 @@ export const InstanceSidebar: FC = () => {
); -}; +}); diff --git a/god-mode/components/forms/ai-form.tsx b/god-mode/components/forms/ai-form.tsx new file mode 100644 index 000000000..87aff342c --- /dev/null +++ b/god-mode/components/forms/ai-form.tsx @@ -0,0 +1,151 @@ +import { FC, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { Eye, EyeOff } from "lucide-react"; +// ui +import { Button, Input } from "@plane/ui"; +// types +import { IFormattedInstanceConfiguration } from "@plane/types"; +// hooks +import useInstance from "hooks/use-instance"; +import useToast from "hooks/use-toast"; + +export interface IInstanceAIForm { + config: IFormattedInstanceConfiguration; +} + +export interface AIFormValues { + OPENAI_API_KEY: string; + GPT_ENGINE: string; +} + +export const InstanceAIForm: FC = (props) => { + const { config } = props; + // states + const [showPassword, setShowPassword] = useState(false); + // store + const { updateInstanceConfigurations } = useInstance(); + // toast + const { setToastAlert } = useToast(); + // form data + const { + handleSubmit, + control, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + OPENAI_API_KEY: config["OPENAI_API_KEY"], + GPT_ENGINE: config["GPT_ENGINE"], + }, + }); + + const onSubmit = async (formData: AIFormValues) => { + const payload: Partial = { ...formData }; + + await updateInstanceConfigurations(payload) + .then(() => + setToastAlert({ + title: "Success", + type: "success", + message: "AI Settings updated successfully", + }) + ) + .catch((err) => console.error(err)); + }; + + return ( + <> +
+
+

GPT_ENGINE

+ ( + + )} + /> +

+ Choose an OpenAI engine.{" "} + + Learn more + +

+
+ +
+

API key

+
+ ( + + )} + /> + {showPassword ? ( + + ) : ( + + )} +
+

+ You will find your API key{" "} + + here. + +

+
+
+ +
+ +
+ + ); +}; diff --git a/god-mode/components/forms/email-form.tsx b/god-mode/components/forms/email-form.tsx new file mode 100644 index 000000000..f92a236fb --- /dev/null +++ b/god-mode/components/forms/email-form.tsx @@ -0,0 +1,266 @@ +import { FC, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +// ui +import { Button, Input, ToggleSwitch } from "@plane/ui"; +import { Eye, EyeOff } from "lucide-react"; +// types +import { IFormattedInstanceConfiguration } from "@plane/types"; +// hooks +import useInstance from "hooks/use-instance"; +import useToast from "hooks/use-toast"; + +export interface IInstanceEmailForm { + config: IFormattedInstanceConfiguration; +} + +export interface EmailFormValues { + EMAIL_HOST: string; + EMAIL_PORT: string; + EMAIL_HOST_USER: string; + EMAIL_HOST_PASSWORD: string; + EMAIL_USE_TLS: string; + // EMAIL_USE_SSL: string; + EMAIL_FROM: string; +} + +export const InstanceEmailForm: FC = (props) => { + const { config } = props; + // states + const [showPassword, setShowPassword] = useState(false); + // store hooks + const { updateInstanceConfigurations } = useInstance(); + // toast + const { setToastAlert } = useToast(); + // form data + const { + handleSubmit, + watch, + control, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + EMAIL_HOST: config["EMAIL_HOST"], + EMAIL_PORT: config["EMAIL_PORT"], + EMAIL_HOST_USER: config["EMAIL_HOST_USER"], + EMAIL_HOST_PASSWORD: config["EMAIL_HOST_PASSWORD"], + EMAIL_USE_TLS: config["EMAIL_USE_TLS"], + // EMAIL_USE_SSL: config["EMAIL_USE_SSL"], + EMAIL_FROM: config["EMAIL_FROM"], + }, + }); + + const onSubmit = async (formData: EmailFormValues) => { + const payload: Partial = { ...formData }; + + await updateInstanceConfigurations(payload) + .then(() => + setToastAlert({ + title: "Success", + type: "success", + message: "Email Settings updated successfully", + }) + ) + .catch((err) => console.error(err)); + }; + + return ( + <> +
+
+

Host

+ ( + + )} + /> +
+ +
+

Port

+ ( + + )} + /> +
+
+
+
+

Username

+ ( + + )} + /> +
+ +
+

Password

+
+ ( + + )} + /> + {showPassword ? ( + + ) : ( + + )} +
+
+
+
+
+

From address

+ ( + + )} + /> +

+ This is the email address your users will see when getting emails + from this instance. You will need to verify this address. +

+
+
+ +
+
+
+
+ Turn TLS{" "} + {Boolean(parseInt(watch("EMAIL_USE_TLS"))) ? "off" : "on"} +
+
+ Use this if your email domain supports TLS. +
+
+
+ ( + { + Boolean(parseInt(value)) === true + ? onChange("0") + : onChange("1"); + }} + size="sm" + /> + )} + /> +
+
+ + {/*
+
+
+ Turn SSL {Boolean(parseInt(watch("EMAIL_USE_SSL"))) ? "off" : "on"} +
+
+ Most email domains support SSL. Use this to secure comms between this instance and your users. +
+
+
+ ( + { + Boolean(parseInt(value)) === true ? onChange("0") : onChange("1"); + }} + size="sm" + /> + )} + /> +
+
*/} +
+ +
+ +
+ + ); +}; diff --git a/god-mode/components/forms/general-form.tsx b/god-mode/components/forms/general-form.tsx new file mode 100644 index 000000000..375219a0a --- /dev/null +++ b/god-mode/components/forms/general-form.tsx @@ -0,0 +1,130 @@ +import { FC } from "react"; +import { Controller, useForm } from "react-hook-form"; +// ui +import { Button, Input } from "@plane/ui"; +// types +import { IInstance, IInstanceAdmin } from "@plane/types"; +// hooks +import useInstance from "hooks/use-instance"; +import useToast from "hooks/use-toast"; + +export interface IInstanceGeneralForm { + instance: IInstance; + instanceAdmins: IInstanceAdmin[]; +} + +export interface GeneralFormValues { + instance_name: string; + // is_telemetry_enabled: boolean; +} + +export const InstanceGeneralForm: FC = (props) => { + const { instance, instanceAdmins } = props; + // store hooks + const { updateInstanceInfo } = useInstance(); + // toast + const { setToastAlert } = useToast(); + // form data + const { + handleSubmit, + control, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + instance_name: instance.instance_name, + // is_telemetry_enabled: instance.is_telemetry_enabled, + }, + }); + + const onSubmit = async (formData: GeneralFormValues) => { + const payload: Partial = { ...formData }; + + await updateInstanceInfo(payload) + .then(() => + setToastAlert({ + title: "Success", + type: "success", + message: "Settings updated successfully", + }) + ) + .catch((err) => console.error(err)); + }; + + return ( + <> +
+
+

Name of instance

+ ( + + )} + /> +
+ +
+

Admin email

+ +
+ +
+

Instance ID

+ +
+
+ + {/*
+
+
Share anonymous usage instance
+
+ Help us understand how you use Plane so we can build better for you. +
+
+
+ } + /> +
+
*/} + +
+ +
+ + ); +}; diff --git a/god-mode/components/forms/github-config-form.tsx b/god-mode/components/forms/github-config-form.tsx new file mode 100644 index 000000000..5f0437dd0 --- /dev/null +++ b/god-mode/components/forms/github-config-form.tsx @@ -0,0 +1,187 @@ +import { FC, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { Copy, Eye, EyeOff } from "lucide-react"; +// ui +import { Button, Input } from "@plane/ui"; +// types +import { IFormattedInstanceConfiguration } from "@plane/types"; +// hooks +import useInstance from "hooks/use-instance"; +import useToast from "hooks/use-toast"; + +export interface IInstanceGithubConfigForm { + config: IFormattedInstanceConfiguration; +} + +export interface GithubConfigFormValues { + GITHUB_CLIENT_ID: string; + GITHUB_CLIENT_SECRET: string; +} + +export const InstanceGithubConfigForm: FC = ( + props +) => { + const { config } = props; + // states + const [showPassword, setShowPassword] = useState(false); + // store hooks + const { updateInstanceConfigurations } = useInstance(); + // toast + const { setToastAlert } = useToast(); + // form data + const { + handleSubmit, + control, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + GITHUB_CLIENT_ID: config["GITHUB_CLIENT_ID"], + GITHUB_CLIENT_SECRET: config["GITHUB_CLIENT_SECRET"], + }, + }); + + const onSubmit = async (formData: GithubConfigFormValues) => { + const payload: Partial = { ...formData }; + + await updateInstanceConfigurations(payload) + .then(() => + setToastAlert({ + title: "Success", + type: "success", + message: "Github Configuration Settings updated successfully", + }) + ) + .catch((err) => console.error(err)); + }; + + const originURL = typeof window !== "undefined" ? window.location.origin : ""; + + return ( +
+
+
+

Client ID

+ ( + + )} + /> +

+ You will get this from your{" "} + + GitHub OAuth application settings. + +

+
+
+

Client secret

+
+ ( + + )} + /> + {showPassword ? ( + + ) : ( + + )} +
+ +

+ Your client secret is also found in your{" "} + + GitHub OAuth application settings. + +

+
+
+

Origin URL

+ +

+ We will auto-generate this. Paste this into the Authorization + callback URL field{" "} + + here. + +

+
+
+
+
+ +
+
+
+ ); +}; diff --git a/god-mode/components/forms/google-config-form.tsx b/god-mode/components/forms/google-config-form.tsx new file mode 100644 index 000000000..59f8d6776 --- /dev/null +++ b/god-mode/components/forms/google-config-form.tsx @@ -0,0 +1,136 @@ +import { FC } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { Copy } from "lucide-react"; +// ui +import { Button, Input } from "@plane/ui"; +// types +import { IFormattedInstanceConfiguration } from "@plane/types"; +// hooks +import useInstance from "hooks/use-instance"; +import useToast from "hooks/use-toast"; + +export interface IInstanceGoogleConfigForm { + config: IFormattedInstanceConfiguration; +} + +export interface GoogleConfigFormValues { + GOOGLE_CLIENT_ID: string; + GOOGLE_CLIENT_SECRET: string; +} + +export const InstanceGoogleConfigForm: FC = ( + props +) => { + const { config } = props; + // store hooks + const { updateInstanceConfigurations } = useInstance(); + // toast + const { setToastAlert } = useToast(); + // form data + const { + handleSubmit, + control, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + GOOGLE_CLIENT_ID: config["GOOGLE_CLIENT_ID"], + GOOGLE_CLIENT_SECRET: config["GOOGLE_CLIENT_SECRET"], + }, + }); + + const onSubmit = async (formData: GoogleConfigFormValues) => { + const payload: Partial = { ...formData }; + + await updateInstanceConfigurations(payload) + .then(() => + setToastAlert({ + title: "Success", + type: "success", + message: "Google Configuration Settings updated successfully", + }) + ) + .catch((err) => console.error(err)); + }; + + const originURL = typeof window !== "undefined" ? window.location.origin : ""; + + return ( +
+
+
+

Client ID

+ ( + + )} + /> +

+ Your client ID lives in your Google API Console.{" "} + + Learn more + +

+
+
+

JavaScript origin URL

+ +

+ We will auto-generate this. Paste this into your Authorized + JavaScript origins field. For this OAuth client{" "} + + here. + +

+
+
+
+
+ +
+
+
+ ); +}; diff --git a/god-mode/components/forms/image-config-form.tsx b/god-mode/components/forms/image-config-form.tsx new file mode 100644 index 000000000..ad6e53889 --- /dev/null +++ b/god-mode/components/forms/image-config-form.tsx @@ -0,0 +1,119 @@ +import { FC, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { Eye, EyeOff } from "lucide-react"; +// ui +import { Button, Input } from "@plane/ui"; +// types +import { IFormattedInstanceConfiguration } from "@plane/types"; +// hooks +import useInstance from "hooks/use-instance"; +import useToast from "hooks/use-toast"; + +export interface IInstanceImageConfigForm { + config: IFormattedInstanceConfiguration; +} + +export interface ImageConfigFormValues { + UNSPLASH_ACCESS_KEY: string; +} + +export const InstanceImageConfigForm: FC = ( + props +) => { + const { config } = props; + // states + const [showPassword, setShowPassword] = useState(false); + // store hooks + const { updateInstanceConfigurations } = useInstance(); + // toast + const { setToastAlert } = useToast(); + // form data + const { + handleSubmit, + control, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + UNSPLASH_ACCESS_KEY: config["UNSPLASH_ACCESS_KEY"], + }, + }); + + const onSubmit = async (formData: ImageConfigFormValues) => { + const payload: Partial = { ...formData }; + + await updateInstanceConfigurations(payload) + .then(() => + setToastAlert({ + title: "Success", + type: "success", + message: "Image Configuration Settings updated successfully", + }) + ) + .catch((err) => console.error(err)); + }; + + return ( + <> +
+
+

Access key from your Unsplash account

+
+ ( + + )} + /> + {showPassword ? ( + + ) : ( + + )} +
+

+ You will find your access key in your Unsplash developer console.{" "} + + Learn more. + +

+
+
+ +
+ +
+ + ); +}; diff --git a/god-mode/components/forms/index.ts b/god-mode/components/forms/index.ts new file mode 100644 index 000000000..92682756a --- /dev/null +++ b/god-mode/components/forms/index.ts @@ -0,0 +1,6 @@ +export * from "./general-form"; +export * from "./ai-form"; +export * from "./email-form"; +export * from "./github-config-form"; +export * from "./google-config-form"; +export * from "./image-config-form"; diff --git a/god-mode/components/sidebar/help-section.tsx b/god-mode/components/sidebar/help-section.tsx index 9925a58b2..c2844e26f 100644 --- a/god-mode/components/sidebar/help-section.tsx +++ b/god-mode/components/sidebar/help-section.tsx @@ -3,7 +3,7 @@ import { Transition } from "@headlessui/react"; import Link from "next/link"; import { FileText, HelpCircle, MessagesSquare, MoveLeft } from "lucide-react"; // hooks -import { useAppTheme } from "hooks/useTheme"; +import { useAppTheme } from "hooks/use-theme"; // icons import { DiscordIcon, GithubIcon } from "@plane/ui"; // assets @@ -43,7 +43,7 @@ export const HelpSection: FC = () => { return (
diff --git a/god-mode/components/sidebar/sidebar-dropdown.tsx b/god-mode/components/sidebar/sidebar-dropdown.tsx index 4ef377628..bf9ef2a99 100644 --- a/god-mode/components/sidebar/sidebar-dropdown.tsx +++ b/god-mode/components/sidebar/sidebar-dropdown.tsx @@ -1,60 +1,48 @@ import { Fragment } from "react"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { useTheme } from "next-themes"; import { observer } from "mobx-react-lite"; -import Link from "next/link"; +import { mutate } from "swr"; // components import { Menu, Transition } from "@headlessui/react"; // icons -import { LogIn, LogOut, Settings, UserCog2 } from "lucide-react"; +import { LogOut, UserCog2, Palette } from "lucide-react"; // hooks -import { useAppTheme } from "hooks/useTheme"; -// hooks -// import useToast from "hooks/use-toast"; +import { useAppTheme } from "hooks/use-theme"; +import useToast from "hooks/use-toast"; +import useUser from "hooks/use-user"; // ui -import { Avatar, Tooltip } from "@plane/ui"; - -// Static Data -const PROFILE_LINKS = [ - { - key: "settings", - name: "Settings", - icon: Settings, - link: `/profile`, - }, -]; +import { Avatar } from "@plane/ui"; export const SidebarDropdown = observer(() => { // router - // const router = useRouter(); + const router = useRouter(); // store hooks const { sidebarCollapsed } = useAppTheme(); - // const { signOut, currentUser, currentUserSettings } = useUser(); + const { signOut, currentUser } = 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 { setToastAlert } = useToast(); + const { resolvedTheme, setTheme } = useTheme(); 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.", - // }) - // ); + 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.", + }) + ); + }; + + const handleThemeSwitch = () => { + const newTheme = resolvedTheme === "dark" ? "light" : "dark"; + setTheme(newTheme); }; return ( @@ -74,30 +62,21 @@ export const SidebarDropdown = observer(() => {

Instance admin

- -
- {/* - - - - */} -
-
)} - {!sidebarCollapsed && ( + {!sidebarCollapsed && currentUser && ( - {/* */} + /> { 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" >
- {/* + {currentUser?.email} - */} - {PROFILE_LINKS.map((link) => ( - - - - - {link.name} - - - - ))} + +
+
+ + + Switch to {resolvedTheme === "dark" ? "light" : "dark"} mode +
{ Sign out
- -
- - {/* - - Exit God Mode - - */} - -
diff --git a/god-mode/components/sidebar/sidebar-menu.tsx b/god-mode/components/sidebar/sidebar-menu.tsx index 9eb20254f..2e71ef3e3 100644 --- a/god-mode/components/sidebar/sidebar-menu.tsx +++ b/god-mode/components/sidebar/sidebar-menu.tsx @@ -2,7 +2,7 @@ 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"; +import { useAppTheme } from "hooks/use-theme"; // ui import { Tooltip } from "@plane/ui"; import { observer } from "mobx-react-lite"; diff --git a/god-mode/components/toast-alert/index.tsx b/god-mode/components/toast-alert/index.tsx new file mode 100644 index 000000000..b4df6ea05 --- /dev/null +++ b/god-mode/components/toast-alert/index.tsx @@ -0,0 +1,61 @@ +import React from "react"; +// hooks +import useToast from "hooks/use-toast"; +// icons +import { AlertTriangle, CheckCircle, Info, X, XCircle } from "lucide-react"; + +const ToastAlerts = () => { + const { alerts, removeAlert } = useToast(); + + if (!alerts) return null; + + return ( +
+ {alerts.map((alert) => ( +
+
+ +
+
+
+
+ {alert.type === "success" ? ( +
+
+

{alert.title}

+ {alert.message &&

{alert.message}

} +
+
+
+
+ ))} +
+ ); +}; + +export default ToastAlerts; diff --git a/god-mode/components/views/general-view.tsx b/god-mode/components/views/general-view.tsx index 72d95ad34..e0e1ae0d5 100644 --- a/god-mode/components/views/general-view.tsx +++ b/god-mode/components/views/general-view.tsx @@ -1,5 +1,13 @@ -export const GeneralView = () => { - // const { instance, instanceAdmins } = useInstance(); +import { observer } from "mobx-react-lite"; +// hooks +import useInstance from "hooks/use-instance"; +// ui +import { Loader } from "@plane/ui"; +// components +import { InstanceGeneralForm } from "components/forms"; + +export const GeneralView = observer(() => { + const { instance, instanceAdmins } = useInstance(); return (
@@ -11,7 +19,7 @@ export const GeneralView = () => { If you have a paid subscription, you will find your license key here.
- {/* {instance && instanceAdmins ? ( + {instance && instanceAdmins ? ( { - )} */} + )} ); -}; +}); diff --git a/god-mode/hooks/useInstance.tsx b/god-mode/hooks/use-instance.tsx similarity index 64% rename from god-mode/hooks/useInstance.tsx rename to god-mode/hooks/use-instance.tsx index 53d427deb..a3cbac80d 100644 --- a/god-mode/hooks/useInstance.tsx +++ b/god-mode/hooks/use-instance.tsx @@ -4,9 +4,11 @@ import { InstanceContext } from "lib/instance-provider"; // types import { IInstanceStore } from "store/instance.store"; -export const useInstance = (): IInstanceStore => { +const useInstance = (): IInstanceStore => { const context = useContext(InstanceContext); if (context === undefined) - throw new Error("useTheme must be used within ThemeProvider"); + throw new Error("useInstance must be used within InstanceProvider"); return context; }; + +export default useInstance; diff --git a/god-mode/hooks/useTheme.tsx b/god-mode/hooks/use-theme.tsx similarity index 92% rename from god-mode/hooks/useTheme.tsx rename to god-mode/hooks/use-theme.tsx index 8f496ba13..bcee73d5f 100644 --- a/god-mode/hooks/useTheme.tsx +++ b/god-mode/hooks/use-theme.tsx @@ -10,3 +10,5 @@ export const useAppTheme = (): IThemeStore => { throw new Error("useTheme must be used within ThemeProvider"); return context; }; + +export default useAppTheme; diff --git a/god-mode/hooks/use-toast.tsx b/god-mode/hooks/use-toast.tsx new file mode 100644 index 000000000..955e96a2f --- /dev/null +++ b/god-mode/hooks/use-toast.tsx @@ -0,0 +1,9 @@ +import { useContext } from "react"; +import { toastContext } from "lib/toast-provider"; + +const useToast = () => { + const toastContextData = useContext(toastContext); + return toastContextData; +}; + +export default useToast; \ No newline at end of file diff --git a/god-mode/hooks/use-user.tsx b/god-mode/hooks/use-user.tsx new file mode 100644 index 000000000..56994f33a --- /dev/null +++ b/god-mode/hooks/use-user.tsx @@ -0,0 +1,14 @@ +import { useContext } from "react"; +// mobx store +import { UserContext } from "lib/user-provider"; +// types +import { IUserStore } from "store/user.store"; + +const useUser = (): IUserStore => { + const context = useContext(UserContext); + if (context === undefined) + throw new Error("useUser must be used within UserProvider"); + return context; +}; + +export default useUser; diff --git a/god-mode/lib/toast-provider.tsx b/god-mode/lib/toast-provider.tsx new file mode 100644 index 000000000..e095f0ef1 --- /dev/null +++ b/god-mode/lib/toast-provider.tsx @@ -0,0 +1,99 @@ +"use client"; + +import React, { createContext, useCallback, useReducer } from "react"; +// uuid +import { v4 as uuid } from "uuid"; +// components +import ToastAlert from "components/toast-alert"; + +export const toastContext = createContext({} as ContextType); + +// types +type ToastAlert = { + id: string; + title: string; + message?: string; + type: "success" | "error" | "warning" | "info"; +}; + +type ReducerActionType = { + type: "SET_TOAST_ALERT" | "REMOVE_TOAST_ALERT"; + payload: ToastAlert; +}; + +type ContextType = { + alerts?: ToastAlert[]; + removeAlert: (id: string) => void; + setToastAlert: (data: { + title: string; + type?: "success" | "error" | "warning" | "info" | undefined; + message?: string | undefined; + }) => void; +}; + +type StateType = { + toastAlerts?: ToastAlert[]; +}; + +type ReducerFunctionType = (state: StateType, action: ReducerActionType) => StateType; + +export const initialState: StateType = { + toastAlerts: [], +}; + +export const reducer: ReducerFunctionType = (state, action) => { + const { type, payload } = action; + + switch (type) { + case "SET_TOAST_ALERT": + return { + ...state, + toastAlerts: [...(state.toastAlerts ?? []), payload], + }; + + case "REMOVE_TOAST_ALERT": + return { + ...state, + toastAlerts: state.toastAlerts?.filter((toastAlert) => toastAlert.id !== payload.id), + }; + + default: { + return state; + } + } +}; + +export const ToastContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [state, dispatch] = useReducer(reducer, initialState); + + const removeAlert = useCallback((id: string) => { + dispatch({ + type: "REMOVE_TOAST_ALERT", + payload: { id, title: "", message: "", type: "success" }, + }); + }, []); + + const setToastAlert = useCallback( + (data: { title: string; type?: "success" | "error" | "warning" | "info"; message?: string }) => { + const id = uuid(); + const { title, type, message } = data; + dispatch({ + type: "SET_TOAST_ALERT", + payload: { id, title, message, type: type ?? "success" }, + }); + + const timer = setTimeout(() => { + removeAlert(id); + clearTimeout(timer); + }, 3000); + }, + [removeAlert] + ); + + return ( + + + {children} + + ); +}; diff --git a/god-mode/lib/user-provider.tsx b/god-mode/lib/user-provider.tsx new file mode 100644 index 000000000..2b1c5683d --- /dev/null +++ b/god-mode/lib/user-provider.tsx @@ -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 { UserStore } from "store/user.store"; + +let userStore = new UserStore(); + +export const UserContext = createContext(userStore); + +const initializeStore = () => { + const _userStore = userStore ?? new UserStore(); + if (typeof window === "undefined") return _userStore; + if (!userStore) userStore = _userStore; + return _userStore; +}; + +export function UserProvider({ children, ...props }: ThemeProviderProps) { + const store = initializeStore(); + return ( + <> + + {children} + + + ); +} diff --git a/god-mode/package.json b/god-mode/package.json index ddca7015d..da31031a8 100644 --- a/god-mode/package.json +++ b/god-mode/package.json @@ -15,8 +15,10 @@ "@plane/ui": "*", "@tailwindcss/typography": "^0.5.9", "autoprefixer": "10.4.14", + "axios": "^1.6.7", "eslint": "8.39.0", "eslint-config-next": "13.3.1", + "js-cookie": "^3.0.5", "mobx": "^6.12.0", "mobx-react-lite": "^4.0.5", "next": "^14.1.0", @@ -25,6 +27,7 @@ "postcss": "8.4.23", "react": "^18.2.0", "react-dom": "^18.2.0", + "swr": "^2.2.4", "tailwindcss": "3.3.2", "typescript": "5.0.4" }, diff --git a/god-mode/services/api.service.ts b/god-mode/services/api.service.ts new file mode 100644 index 000000000..23be79929 --- /dev/null +++ b/god-mode/services/api.service.ts @@ -0,0 +1,94 @@ +import axios from "axios"; +import Cookies from "js-cookie"; + +export abstract class APIService { + protected baseURL: string; + protected headers: any = {}; + + constructor(baseURL: string) { + this.baseURL = baseURL; + } + + setRefreshToken(token: string) { + Cookies.set("refreshToken", token, { expires: 30 }); + } + + getRefreshToken() { + return Cookies.get("refreshToken"); + } + + purgeRefreshToken() { + Cookies.remove("refreshToken", { path: "/" }); + } + + setAccessToken(token: string) { + Cookies.set("accessToken", token, { expires: 30 }); + } + + getAccessToken() { + return Cookies.get("accessToken"); + } + + purgeAccessToken() { + Cookies.remove("accessToken", { path: "/" }); + } + + getHeaders() { + return { + Authorization: `Bearer ${this.getAccessToken()}`, + }; + } + + get(url: string, config = {}): Promise { + return axios({ + method: "get", + url: this.baseURL + url, + headers: this.getAccessToken() ? this.getHeaders() : {}, + ...config, + }); + } + + post(url: string, data = {}, config = {}): Promise { + return axios({ + method: "post", + url: this.baseURL + url, + data, + headers: this.getAccessToken() ? this.getHeaders() : {}, + ...config, + }); + } + + put(url: string, data = {}, config = {}): Promise { + return axios({ + method: "put", + url: this.baseURL + url, + data, + headers: this.getAccessToken() ? this.getHeaders() : {}, + ...config, + }); + } + + patch(url: string, data = {}, config = {}): Promise { + return axios({ + method: "patch", + url: this.baseURL + url, + data, + headers: this.getAccessToken() ? this.getHeaders() : {}, + ...config, + }); + } + + delete(url: string, data?: any, config = {}): Promise { + return axios({ + method: "delete", + url: this.baseURL + url, + data: data, + headers: this.getAccessToken() ? this.getHeaders() : {}, + ...config, + }); + } + + request(config = {}) { + return axios(config); + } +} diff --git a/god-mode/services/auth.service.ts b/god-mode/services/auth.service.ts new file mode 100644 index 000000000..81b694c78 --- /dev/null +++ b/god-mode/services/auth.service.ts @@ -0,0 +1,148 @@ +// services +import { APIService } from "services/api.service"; +// types +import { + IEmailCheckData, + IEmailCheckResponse, + ILoginTokenResponse, + IMagicSignInData, + IPasswordSignInData, +} from "@plane/types"; + +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ? process.env.NEXT_PUBLIC_API_BASE_URL : ""; + +export class AuthService extends APIService { + constructor() { + super(API_BASE_URL); + } + + async emailCheck(data: IEmailCheckData): Promise { + return this.post("/api/email-check/", data, { headers: {} }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async passwordSignIn(data: IPasswordSignInData): Promise { + return this.post("/api/sign-in/", data, { headers: {} }) + .then((response) => { + this.setAccessToken(response?.data?.access_token); + this.setRefreshToken(response?.data?.refresh_token); + return response?.data; + }) + .catch((error) => { + throw error?.response?.data; + }); + } + + async sendResetPasswordLink(data: { email: string }): Promise { + return this.post(`/api/forgot-password/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + async setPassword(data: { password: string }): Promise { + return this.post(`/api/users/me/set-password/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async resetPassword( + uidb64: string, + token: string, + data: { + new_password: string; + } + ): Promise { + return this.post(`/api/reset-password/${uidb64}/${token}/`, data, { headers: {} }) + .then((response) => { + if (response?.status === 200) { + this.setAccessToken(response?.data?.access_token); + this.setRefreshToken(response?.data?.refresh_token); + return response?.data; + } + }) + .catch((error) => { + throw error?.response?.data; + }); + } + + async emailSignUp(data: { email: string; password: string }): Promise { + return this.post("/api/sign-up/", data, { headers: {} }) + .then((response) => { + this.setAccessToken(response?.data?.access_token); + this.setRefreshToken(response?.data?.refresh_token); + return response?.data; + }) + .catch((error) => { + throw error?.response?.data; + }); + } + + async socialAuth(data: any): Promise { + return this.post("/api/social-auth/", data, { headers: {} }) + .then((response) => { + this.setAccessToken(response?.data?.access_token); + this.setRefreshToken(response?.data?.refresh_token); + return response?.data; + }) + .catch((error) => { + throw error?.response?.data; + }); + } + + async generateUniqueCode(data: { email: string }): Promise { + return this.post("/api/magic-generate/", data, { headers: {} }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async magicSignIn(data: IMagicSignInData): Promise { + return await this.post("/api/magic-sign-in/", data, { headers: {} }) + .then((response) => { + if (response?.status === 200) { + this.setAccessToken(response?.data?.access_token); + this.setRefreshToken(response?.data?.refresh_token); + return response?.data; + } + }) + .catch((error) => { + throw error?.response?.data; + }); + } + + async instanceAdminSignIn(data: IPasswordSignInData): Promise { + return await this.post("/api/instances/admins/sign-in/", data, { headers: {} }) + .then((response) => { + if (response?.status === 200) { + this.setAccessToken(response?.data?.access_token); + this.setRefreshToken(response?.data?.refresh_token); + return response?.data; + } + }) + .catch((error) => { + throw error?.response?.data; + }); + } + + async signOut(): Promise { + return this.post("/api/sign-out/", { refresh_token: this.getRefreshToken() }) + .then((response) => { + this.purgeAccessToken(); + this.purgeRefreshToken(); + return response?.data; + }) + .catch((error) => { + this.purgeAccessToken(); + this.purgeRefreshToken(); + throw error?.response?.data; + }); + } +} diff --git a/god-mode/services/instance.service.ts b/god-mode/services/instance.service.ts new file mode 100644 index 000000000..2fd478d4a --- /dev/null +++ b/god-mode/services/instance.service.ts @@ -0,0 +1,53 @@ +import { APIService } from "services/api.service"; +// types +import type { IFormattedInstanceConfiguration, IInstance, IInstanceAdmin, IInstanceConfiguration } from "@plane/types"; + +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ? process.env.NEXT_PUBLIC_API_BASE_URL : ""; + +export class InstanceService extends APIService { + constructor() { + super(API_BASE_URL); + } + + async getInstanceInfo(): Promise { + return this.get("/api/instances/", { headers: {} }) + .then((response) => response.data) + .catch((error) => { + throw error; + }); + } + + async getInstanceAdmins(): Promise { + return this.get("/api/instances/admins/") + .then((response) => response.data) + .catch((error) => { + throw error; + }); + } + + async updateInstanceInfo(data: Partial): Promise { + return this.patch("/api/instances/", data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async getInstanceConfigurations() { + return this.get("/api/instances/configurations/") + .then((response) => response.data) + .catch((error) => { + throw error; + }); + } + + async updateInstanceConfigurations( + data: Partial + ): Promise { + return this.patch("/api/instances/configurations/", data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/god-mode/services/user.service.ts b/god-mode/services/user.service.ts new file mode 100644 index 000000000..520881cfe --- /dev/null +++ b/god-mode/services/user.service.ts @@ -0,0 +1,37 @@ +// services +import { APIService } from "services/api.service"; +// types +import type { IUser, IInstanceAdminStatus } from "@plane/types"; + +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL + ? process.env.NEXT_PUBLIC_API_BASE_URL + : ""; + +export class UserService extends APIService { + constructor() { + super(API_BASE_URL); + } + + currentUserConfig() { + return { + url: `${this.baseURL}/api/users/me/`, + headers: this.getHeaders(), + }; + } + + async currentUser(): Promise { + return this.get("/api/users/me/") + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + async currentUserInstanceAdminStatus(): Promise { + return this.get("/api/users/me/instance-admin/") + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } +} diff --git a/god-mode/store/instance.store.ts b/god-mode/store/instance.store.ts index 821dfff28..be956b14d 100644 --- a/god-mode/store/instance.store.ts +++ b/god-mode/store/instance.store.ts @@ -1,14 +1,149 @@ -// mobx -import { action, observable, makeObservable } from "mobx"; +import { + observable, + action, + computed, + makeObservable, + runInAction, +} from "mobx"; +// types +import { + IInstance, + IInstanceConfiguration, + IFormattedInstanceConfiguration, + IInstanceAdmin, +} from "@plane/types"; +// services +import { InstanceService } from "services/instance.service"; -export interface IInstanceStore {} +export interface IInstanceStore { + // issues + instance: IInstance | null; + instanceAdmins: IInstanceAdmin[] | null; + configurations: IInstanceConfiguration[] | null; + // computed + formattedConfig: IFormattedInstanceConfiguration | null; + // action + fetchInstanceInfo: () => Promise; + fetchInstanceAdmins: () => Promise; + updateInstanceInfo: (data: Partial) => Promise; + fetchInstanceConfigurations: () => Promise; + updateInstanceConfigurations: ( + data: Partial + ) => Promise; +} export class InstanceStore implements IInstanceStore { + instance: IInstance | null = null; + instanceAdmins: IInstanceAdmin[] | null = null; + configurations: IInstanceConfiguration[] | null = null; + // service + instanceService; + constructor() { makeObservable(this, { // observable - // action + instance: observable, + instanceAdmins: observable, + configurations: observable, // computed + formattedConfig: computed, + // actions + fetchInstanceInfo: action, + fetchInstanceAdmins: action, + updateInstanceInfo: action, + fetchInstanceConfigurations: action, + updateInstanceConfigurations: action, }); + + this.instanceService = new InstanceService(); } + + /** + * computed value for instance configurations data for forms. + * @returns configurations in the form of {key, value} pair. + */ + get formattedConfig() { + if (!this.configurations) return null; + return this.configurations?.reduce( + (formData: IFormattedInstanceConfiguration, config) => { + formData[config.key] = config.value; + return formData; + }, + {} + ); + } + + /** + * fetch instance info from API + */ + fetchInstanceInfo = async () => { + try { + const instance = await this.instanceService.getInstanceInfo(); + runInAction(() => { + this.instance = instance; + }); + return instance; + } catch (error) { + console.log("Error while fetching the instance info"); + throw error; + } + }; + + /** + * fetch instance admins from API + */ + fetchInstanceAdmins = async () => { + const instanceAdmins = await this.instanceService.getInstanceAdmins(); + runInAction(() => { + this.instanceAdmins = instanceAdmins; + }); + return instanceAdmins; + }; + + /** + * update instance info + * @param data + */ + updateInstanceInfo = async (data: Partial) => + await this.instanceService.updateInstanceInfo(data).then((response) => { + runInAction(() => { + this.instance = response; + }); + return response; + }); + + /** + * fetch instance configurations from API + */ + fetchInstanceConfigurations = async () => { + try { + const configurations = + await this.instanceService.getInstanceConfigurations(); + runInAction(() => { + this.configurations = configurations; + }); + return configurations; + } catch (error) { + console.log("Error while fetching the instance configurations"); + throw error; + } + }; + + /** + * update instance configurations + * @param data + */ + updateInstanceConfigurations = async ( + data: Partial + ) => + await this.instanceService + .updateInstanceConfigurations(data) + .then((response) => { + runInAction(() => { + this.configurations = this.configurations + ? [...this.configurations, ...response] + : response; + }); + return response; + }); } diff --git a/god-mode/store/user.store.ts b/god-mode/store/user.store.ts new file mode 100644 index 000000000..019291f78 --- /dev/null +++ b/god-mode/store/user.store.ts @@ -0,0 +1,100 @@ +import { action, observable, runInAction, makeObservable } from "mobx"; +// services +import { UserService } from "services/user.service"; +import { AuthService } from "services/auth.service"; +// interfaces +import { IUser } from "@plane/types"; + +export interface IUserStore { + // states + currentUserError: any | null; + currentUserLoader: boolean; + // observables + isUserLoggedIn: boolean | null; + currentUser: IUser | null; + isUserInstanceAdmin: boolean | null; + // fetch actions + fetchCurrentUser: () => Promise; + fetchCurrentUserInstanceAdminStatus: () => Promise; + + signOut: () => Promise; +} + +export class UserStore implements IUserStore { + // states + currentUserError: any | null = null; + currentUserLoader: boolean = false; + // observables + isUserLoggedIn: boolean | null = null; + currentUser: IUser | null = null; + isUserInstanceAdmin: boolean | null = null; + + // services + userService; + authService; + + constructor() { + makeObservable(this, { + // states + currentUserError: observable.ref, + currentUserLoader: observable.ref, + // observable + currentUser: observable, + isUserInstanceAdmin: observable.ref, + // action + fetchCurrentUser: action, + fetchCurrentUserInstanceAdminStatus: action, + signOut: action, + }); + this.userService = new UserService(); + this.authService = new AuthService(); + } + + /** + * Fetches the current user + * @returns Promise + */ + fetchCurrentUser = async () => { + try { + this.currentUserLoader = true; + const response = await this.userService.currentUser(); + runInAction(() => { + this.isUserLoggedIn = true; + this.currentUser = response; + this.currentUserError = null; + this.currentUserLoader = false; + }); + return response; + } catch (error) { + runInAction(() => { + this.currentUserLoader = false; + this.currentUserError = error; + }); + throw error; + } + }; + + /** + * Fetches the current user instance admin status + * @returns Promise + */ + fetchCurrentUserInstanceAdminStatus = async () => + await this.userService.currentUserInstanceAdminStatus().then((response) => { + runInAction(() => { + this.isUserInstanceAdmin = response.is_instance_admin; + }); + return response.is_instance_admin; + }); + + /** + * Signs out the current user + * @returns Promise + */ + signOut = async () => + await this.authService.signOut().then(() => { + runInAction(() => { + this.currentUser = null; + this.isUserLoggedIn = false; + }); + }); +} diff --git a/packages/tailwind-config-custom/tailwind.config.js b/packages/tailwind-config-custom/tailwind.config.js index 3465b8196..b0c3be057 100644 --- a/packages/tailwind-config-custom/tailwind.config.js +++ b/packages/tailwind-config-custom/tailwind.config.js @@ -10,6 +10,7 @@ module.exports = { "./constants/**/*.{js,ts,jsx,tsx}", "./layouts/**/*.tsx", "./pages/**/*.tsx", + "./app/**/*.tsx", "./ui/**/*.tsx", "../packages/ui/**/*.{js,ts,jsx,tsx}", "../packages/editor/**/src/**/*.{js,ts,jsx,tsx}", diff --git a/yarn.lock b/yarn.lock index 1dd8ebf87..8ea9714c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3422,6 +3422,15 @@ axios@^1.1.3, axios@^1.3.4: form-data "^4.0.0" proxy-from-env "^1.1.0" +axios@^1.6.7: + version "1.6.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" + integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== + dependencies: + follow-redirects "^1.15.4" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" @@ -5271,6 +5280,11 @@ follow-redirects@^1.15.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== +follow-redirects@^1.15.4: + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -6065,7 +6079,7 @@ joycon@^3.0.1: resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== -js-cookie@^3.0.1: +js-cookie@^3.0.1, js-cookie@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== @@ -8625,7 +8639,7 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -swr@^2.1.3, swr@^2.2.2: +swr@^2.1.3, swr@^2.2.2, swr@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.4.tgz#03ec4c56019902fbdc904d78544bd7a9a6fa3f07" integrity sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==