mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: god mode settings.
This commit is contained in:
parent
61b8ea4554
commit
5a6a754a70
@ -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 (
|
||||
<div className="flex flex-col gap-8">
|
||||
@ -35,7 +28,7 @@ const InstanceAIPage = observer(() => {
|
||||
for all your workspaces.
|
||||
</div>
|
||||
</div>
|
||||
{/* {formattedConfig ? (
|
||||
{formattedConfig ? (
|
||||
<>
|
||||
<div>
|
||||
<div className="pb-1 text-xl font-medium text-custom-text-100">
|
||||
@ -64,7 +57,7 @@ const InstanceAIPage = observer(() => {
|
||||
</div>
|
||||
<Loader.Item height="50px" />
|
||||
</Loader>
|
||||
)} */}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -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<boolean>(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 (
|
||||
<div className="flex flex-col gap-8">
|
||||
@ -82,7 +74,7 @@ const InstanceAuthorizationPage = observer(() => {
|
||||
and GitHub accounts, and below are the settings.
|
||||
</div>
|
||||
</div>
|
||||
{/* {formattedConfig ? (
|
||||
{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">
|
||||
@ -166,7 +158,7 @@ const InstanceAuthorizationPage = observer(() => {
|
||||
</div>
|
||||
<Loader.Item height="50px" />
|
||||
</Loader>
|
||||
)} */}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -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 (
|
||||
<div className="flex flex-col gap-8">
|
||||
@ -38,7 +32,7 @@ const InstanceEmailPage = observer(() => {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* {formattedConfig ? (
|
||||
{formattedConfig ? (
|
||||
<InstanceEmailForm config={formattedConfig} />
|
||||
) : (
|
||||
<Loader className="space-y-4">
|
||||
@ -48,7 +42,7 @@ const InstanceEmailPage = observer(() => {
|
||||
</div>
|
||||
<Loader.Item height="50px" />
|
||||
</Loader>
|
||||
)} */}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ export const InstanceHeader: FC = observer(() => {
|
||||
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">
|
||||
<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-sidebar-border-200 bg-custom-sidebar-background-100 p-4">
|
||||
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
|
||||
{title && (
|
||||
<div>
|
||||
|
@ -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 (
|
||||
<div className="flex flex-col gap-8">
|
||||
@ -32,7 +25,7 @@ const InstanceImagePage = observer(() => {
|
||||
Let your users search and choose images from third-party libraries
|
||||
</div>
|
||||
</div>
|
||||
{/* {formattedConfig ? (
|
||||
{formattedConfig ? (
|
||||
<InstanceImageConfigForm config={formattedConfig} />
|
||||
) : (
|
||||
<Loader className="space-y-4">
|
||||
@ -42,7 +35,7 @@ const InstanceImagePage = observer(() => {
|
||||
</div>
|
||||
<Loader.Item height="50px" />
|
||||
</Loader>
|
||||
)} */}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -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 (
|
||||
<html lang="en">
|
||||
<body className={`antialiased`}>
|
||||
{/* <AuthWrapper> */}
|
||||
{/* {response?.is_instance_admin ? (
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||
{/* {isUserInstanceAdmin || true ? ( */}
|
||||
<ThemeProvider
|
||||
themes={["light", "dark"]}
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
>
|
||||
<ToastContextProvider>
|
||||
<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">
|
||||
@ -41,24 +40,14 @@ export default async function RootLayout({ children }: RootLayoutProps) {
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
) : (
|
||||
</ToastContextProvider>
|
||||
</ThemeProvider>
|
||||
{/* ) : (
|
||||
<div>Login</div>
|
||||
)} */}
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default RootLayout;
|
||||
|
@ -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 (
|
||||
<div className="flex">
|
||||
<GeneralView />
|
||||
|
@ -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<IInstanceSidebar> = () => {
|
||||
export const InstanceSidebar: FC<IInstanceSidebar> = observer(() => {
|
||||
// store
|
||||
const { sidebarCollapsed } = useAppTheme();
|
||||
|
||||
@ -25,4 +26,4 @@ export const InstanceSidebar: FC<IInstanceSidebar> = () => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
151
god-mode/components/forms/ai-form.tsx
Normal file
151
god-mode/components/forms/ai-form.tsx
Normal file
@ -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<IInstanceAIForm> = (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<AIFormValues>({
|
||||
defaultValues: {
|
||||
OPENAI_API_KEY: config["OPENAI_API_KEY"],
|
||||
GPT_ENGINE: config["GPT_ENGINE"],
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: AIFormValues) => {
|
||||
const payload: Partial<AIFormValues> = { ...formData };
|
||||
|
||||
await updateInstanceConfigurations(payload)
|
||||
.then(() =>
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "AI Settings updated successfully",
|
||||
})
|
||||
)
|
||||
.catch((err) => console.error(err));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid-col grid w-full grid-cols-1 items-center justify-between gap-x-16 gap-y-8 lg:grid-cols-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">GPT_ENGINE</h4>
|
||||
<Controller
|
||||
control={control}
|
||||
name="GPT_ENGINE"
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="GPT_ENGINE"
|
||||
name="GPT_ENGINE"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.GPT_ENGINE)}
|
||||
placeholder="gpt-3.5-turbo"
|
||||
className="w-full rounded-md font-medium"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<p className="text-xs text-custom-text-400">
|
||||
Choose an OpenAI engine.{" "}
|
||||
<a
|
||||
href="https://platform.openai.com/docs/models/overview"
|
||||
target="_blank"
|
||||
className="text-custom-primary-100 hover:underline"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">API key</h4>
|
||||
<div className="relative">
|
||||
<Controller
|
||||
control={control}
|
||||
name="OPENAI_API_KEY"
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="OPENAI_API_KEY"
|
||||
name="OPENAI_API_KEY"
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.OPENAI_API_KEY)}
|
||||
placeholder="sk-asddassdfasdefqsdfasd23das3dasdcasd"
|
||||
className="w-full rounded-md !pr-10 font-medium"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{showPassword ? (
|
||||
<button
|
||||
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
|
||||
onClick={() => setShowPassword(false)}
|
||||
>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
|
||||
onClick={() => setShowPassword(true)}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-custom-text-400">
|
||||
You will find your API key{" "}
|
||||
<a
|
||||
href="https://platform.openai.com/api-keys"
|
||||
target="_blank"
|
||||
className="text-custom-primary-100 hover:underline"
|
||||
rel="noreferrer"
|
||||
>
|
||||
here.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center py-1">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Saving..." : "Save changes"}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
266
god-mode/components/forms/email-form.tsx
Normal file
266
god-mode/components/forms/email-form.tsx
Normal file
@ -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<IInstanceEmailForm> = (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<EmailFormValues>({
|
||||
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<EmailFormValues> = { ...formData };
|
||||
|
||||
await updateInstanceConfigurations(payload)
|
||||
.then(() =>
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "Email Settings updated successfully",
|
||||
})
|
||||
)
|
||||
.catch((err) => console.error(err));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid-col grid w-full max-w-4xl grid-cols-1 items-center justify-between gap-x-20 gap-y-10 lg:grid-cols-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Host</h4>
|
||||
<Controller
|
||||
control={control}
|
||||
name="EMAIL_HOST"
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="EMAIL_HOST"
|
||||
name="EMAIL_HOST"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.EMAIL_HOST)}
|
||||
placeholder="email.google.com"
|
||||
className="w-full rounded-md font-medium"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Port</h4>
|
||||
<Controller
|
||||
control={control}
|
||||
name="EMAIL_PORT"
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="EMAIL_PORT"
|
||||
name="EMAIL_PORT"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.EMAIL_PORT)}
|
||||
placeholder="8080"
|
||||
className="w-full rounded-md font-medium"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid-col grid w-full max-w-4xl grid-cols-1 items-center justify-between gap-x-20 gap-y-10 lg:grid-cols-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Username</h4>
|
||||
<Controller
|
||||
control={control}
|
||||
name="EMAIL_HOST_USER"
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="EMAIL_HOST_USER"
|
||||
name="EMAIL_HOST_USER"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.EMAIL_HOST_USER)}
|
||||
placeholder="getitdone@projectplane.so"
|
||||
className="w-full rounded-md font-medium"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Password</h4>
|
||||
<div className="relative">
|
||||
<Controller
|
||||
control={control}
|
||||
name="EMAIL_HOST_PASSWORD"
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="EMAIL_HOST_PASSWORD"
|
||||
name="EMAIL_HOST_PASSWORD"
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.EMAIL_HOST_PASSWORD)}
|
||||
placeholder="Password"
|
||||
className="w-full rounded-md !pr-10 font-medium"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{showPassword ? (
|
||||
<button
|
||||
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
|
||||
onClick={() => setShowPassword(false)}
|
||||
>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
|
||||
onClick={() => setShowPassword(true)}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid-col grid w-full max-w-4xl grid-cols-1 items-center justify-between gap-x-20 gap-y-10 lg:grid-cols-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">From address</h4>
|
||||
<Controller
|
||||
control={control}
|
||||
name="EMAIL_FROM"
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="EMAIL_FROM"
|
||||
name="EMAIL_FROM"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.EMAIL_FROM)}
|
||||
placeholder="no-reply@projectplane.so"
|
||||
className="w-full rounded-md font-medium"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<p className="text-xs text-custom-text-400">
|
||||
This is the email address your users will see when getting emails
|
||||
from this instance. You will need to verify this address.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full max-w-md flex-col gap-y-10 px-1">
|
||||
<div className="mr-8 flex items-center gap-10 pt-4">
|
||||
<div className="grow">
|
||||
<div className="text-sm font-medium text-custom-text-100">
|
||||
Turn TLS{" "}
|
||||
{Boolean(parseInt(watch("EMAIL_USE_TLS"))) ? "off" : "on"}
|
||||
</div>
|
||||
<div className="text-xs font-normal text-custom-text-300">
|
||||
Use this if your email domain supports TLS.
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<Controller
|
||||
control={control}
|
||||
name="EMAIL_USE_TLS"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ToggleSwitch
|
||||
value={Boolean(parseInt(value))}
|
||||
onChange={() => {
|
||||
Boolean(parseInt(value)) === true
|
||||
? onChange("0")
|
||||
: onChange("1");
|
||||
}}
|
||||
size="sm"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div className="flex items-center gap-10 pt-4 mr-8">
|
||||
<div className="grow">
|
||||
<div className="text-custom-text-100 font-medium text-sm">
|
||||
Turn SSL {Boolean(parseInt(watch("EMAIL_USE_SSL"))) ? "off" : "on"}
|
||||
</div>
|
||||
<div className="text-custom-text-300 font-normal text-xs">
|
||||
Most email domains support SSL. Use this to secure comms between this instance and your users.
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<Controller
|
||||
control={control}
|
||||
name="EMAIL_USE_SSL"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ToggleSwitch
|
||||
value={Boolean(parseInt(value))}
|
||||
onChange={() => {
|
||||
Boolean(parseInt(value)) === true ? onChange("0") : onChange("1");
|
||||
}}
|
||||
size="sm"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
<div className="flex max-w-4xl items-center py-1">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Saving..." : "Save changes"}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
130
god-mode/components/forms/general-form.tsx
Normal file
130
god-mode/components/forms/general-form.tsx
Normal file
@ -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<IInstanceGeneralForm> = (props) => {
|
||||
const { instance, instanceAdmins } = props;
|
||||
// store hooks
|
||||
const { updateInstanceInfo } = useInstance();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<GeneralFormValues>({
|
||||
defaultValues: {
|
||||
instance_name: instance.instance_name,
|
||||
// is_telemetry_enabled: instance.is_telemetry_enabled,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: GeneralFormValues) => {
|
||||
const payload: Partial<GeneralFormValues> = { ...formData };
|
||||
|
||||
await updateInstanceInfo(payload)
|
||||
.then(() =>
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "Settings updated successfully",
|
||||
})
|
||||
)
|
||||
.catch((err) => console.error(err));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid-col grid w-full grid-cols-1 items-center justify-between gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Name of instance</h4>
|
||||
<Controller
|
||||
control={control}
|
||||
name="instance_name"
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="instance_name"
|
||||
name="instance_name"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.instance_name)}
|
||||
placeholder="Instance Name"
|
||||
className="w-full rounded-md font-medium"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Admin email</h4>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
value={instanceAdmins[0].user_detail.email ?? ""}
|
||||
placeholder="Admin email"
|
||||
className="w-full cursor-not-allowed !text-custom-text-400"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Instance ID</h4>
|
||||
<Input
|
||||
id="instance_id"
|
||||
name="instance_id"
|
||||
type="text"
|
||||
value={instance.instance_id}
|
||||
className="w-full cursor-not-allowed rounded-md font-medium !text-custom-text-400"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div className="flex items-center gap-12 pt-4">
|
||||
<div>
|
||||
<div className="text-custom-text-100 font-medium text-sm">Share anonymous usage instance</div>
|
||||
<div className="text-custom-text-300 font-normal text-xs">
|
||||
Help us understand how you use Plane so we can build better for you.
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="is_telemetry_enabled"
|
||||
render={({ field: { value, onChange } }) => <ToggleSwitch value={value} onChange={onChange} size="sm" />}
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="flex items-center py-1">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Saving..." : "Save changes"}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
187
god-mode/components/forms/github-config-form.tsx
Normal file
187
god-mode/components/forms/github-config-form.tsx
Normal file
@ -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<IInstanceGithubConfigForm> = (
|
||||
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<GithubConfigFormValues>({
|
||||
defaultValues: {
|
||||
GITHUB_CLIENT_ID: config["GITHUB_CLIENT_ID"],
|
||||
GITHUB_CLIENT_SECRET: config["GITHUB_CLIENT_SECRET"],
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: GithubConfigFormValues) => {
|
||||
const payload: Partial<GithubConfigFormValues> = { ...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 (
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="grid-col grid w-full grid-cols-1 justify-between gap-x-12 gap-y-8 lg:grid-cols-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Client ID</h4>
|
||||
<Controller
|
||||
control={control}
|
||||
name="GITHUB_CLIENT_ID"
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="GITHUB_CLIENT_ID"
|
||||
name="GITHUB_CLIENT_ID"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.GITHUB_CLIENT_ID)}
|
||||
placeholder="70a44354520df8bd9bcd"
|
||||
className="w-full rounded-md font-medium"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<p className="text-xs text-custom-text-400">
|
||||
You will get this from your{" "}
|
||||
<a
|
||||
href="https://github.com/settings/applications/new"
|
||||
target="_blank"
|
||||
className="text-custom-primary-100 hover:underline"
|
||||
rel="noreferrer"
|
||||
>
|
||||
GitHub OAuth application settings.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Client secret</h4>
|
||||
<div className="relative">
|
||||
<Controller
|
||||
control={control}
|
||||
name="GITHUB_CLIENT_SECRET"
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="GITHUB_CLIENT_SECRET"
|
||||
name="GITHUB_CLIENT_SECRET"
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.GITHUB_CLIENT_SECRET)}
|
||||
placeholder="9b0050f94ec1b744e32ce79ea4ffacd40d4119cb"
|
||||
className="w-full rounded-md !pr-10 font-medium"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{showPassword ? (
|
||||
<button
|
||||
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
|
||||
onClick={() => setShowPassword(false)}
|
||||
>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
|
||||
onClick={() => setShowPassword(true)}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-custom-text-400">
|
||||
Your client secret is also found in your{" "}
|
||||
<a
|
||||
href="https://github.com/settings/applications/new"
|
||||
target="_blank"
|
||||
className="text-custom-primary-100 hover:underline"
|
||||
rel="noreferrer"
|
||||
>
|
||||
GitHub OAuth application settings.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Origin URL</h4>
|
||||
<Button
|
||||
variant="neutral-primary"
|
||||
className="flex items-center justify-between py-2"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(originURL);
|
||||
setToastAlert({
|
||||
message:
|
||||
"The Origin URL has been successfully copied to your clipboard",
|
||||
type: "success",
|
||||
title: "Copied to clipboard",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<p className="text-sm font-medium">{originURL}</p>
|
||||
<Copy size={18} color="#B9B9B9" />
|
||||
</Button>
|
||||
<p className="text-xs text-custom-text-400">
|
||||
We will auto-generate this. Paste this into the Authorization
|
||||
callback URL field{" "}
|
||||
<a
|
||||
href="https://github.com/settings/applications/new"
|
||||
target="_blank"
|
||||
className="text-custom-primary-100 hover:underline"
|
||||
rel="noreferrer"
|
||||
>
|
||||
here.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Saving..." : "Save changes"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
136
god-mode/components/forms/google-config-form.tsx
Normal file
136
god-mode/components/forms/google-config-form.tsx
Normal file
@ -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<IInstanceGoogleConfigForm> = (
|
||||
props
|
||||
) => {
|
||||
const { config } = props;
|
||||
// store hooks
|
||||
const { updateInstanceConfigurations } = useInstance();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<GoogleConfigFormValues>({
|
||||
defaultValues: {
|
||||
GOOGLE_CLIENT_ID: config["GOOGLE_CLIENT_ID"],
|
||||
GOOGLE_CLIENT_SECRET: config["GOOGLE_CLIENT_SECRET"],
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: GoogleConfigFormValues) => {
|
||||
const payload: Partial<GoogleConfigFormValues> = { ...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 (
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="grid-col grid w-full grid-cols-1 justify-between gap-x-12 gap-y-8 lg:grid-cols-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Client ID</h4>
|
||||
<Controller
|
||||
control={control}
|
||||
name="GOOGLE_CLIENT_ID"
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="GOOGLE_CLIENT_ID"
|
||||
name="GOOGLE_CLIENT_ID"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.GOOGLE_CLIENT_ID)}
|
||||
placeholder="840195096245-0p2tstej9j5nc4l8o1ah2dqondscqc1g.apps.googleusercontent.com"
|
||||
className="w-full rounded-md font-medium"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<p className="text-xs text-custom-text-400">
|
||||
Your client ID lives in your Google API Console.{" "}
|
||||
<a
|
||||
href="https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow#creatingcred"
|
||||
target="_blank"
|
||||
className="text-custom-primary-100 hover:underline"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">JavaScript origin URL</h4>
|
||||
<Button
|
||||
variant="neutral-primary"
|
||||
className="flex items-center justify-between py-2"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(originURL);
|
||||
setToastAlert({
|
||||
message:
|
||||
"The Origin URL has been successfully copied to your clipboard",
|
||||
type: "success",
|
||||
title: "Copied to clipboard",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<p className="text-sm font-medium">{originURL}</p>
|
||||
<Copy size={18} color="#B9B9B9" />
|
||||
</Button>
|
||||
<p className="text-xs text-custom-text-400">
|
||||
We will auto-generate this. Paste this into your Authorized
|
||||
JavaScript origins field. For this OAuth client{" "}
|
||||
<a
|
||||
href="https://console.cloud.google.com/apis/credentials/oauthclient"
|
||||
target="_blank"
|
||||
className="text-custom-primary-100 hover:underline"
|
||||
rel="noreferrer"
|
||||
>
|
||||
here.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Saving..." : "Save changes"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
119
god-mode/components/forms/image-config-form.tsx
Normal file
119
god-mode/components/forms/image-config-form.tsx
Normal file
@ -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<IInstanceImageConfigForm> = (
|
||||
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<ImageConfigFormValues>({
|
||||
defaultValues: {
|
||||
UNSPLASH_ACCESS_KEY: config["UNSPLASH_ACCESS_KEY"],
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: ImageConfigFormValues) => {
|
||||
const payload: Partial<ImageConfigFormValues> = { ...formData };
|
||||
|
||||
await updateInstanceConfigurations(payload)
|
||||
.then(() =>
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "Image Configuration Settings updated successfully",
|
||||
})
|
||||
)
|
||||
.catch((err) => console.error(err));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid-col grid w-full grid-cols-1 items-center justify-between gap-x-16 gap-y-8 lg:grid-cols-2">
|
||||
<div className="flex max-w-md flex-col gap-1">
|
||||
<h4 className="text-sm">Access key from your Unsplash account</h4>
|
||||
<div className="relative">
|
||||
<Controller
|
||||
control={control}
|
||||
name="UNSPLASH_ACCESS_KEY"
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="UNSPLASH_ACCESS_KEY"
|
||||
name="UNSPLASH_ACCESS_KEY"
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.UNSPLASH_ACCESS_KEY)}
|
||||
placeholder="oXgq-sdfadsaeweqasdfasdf3234234rassd"
|
||||
className="w-full rounded-md !pr-10 font-medium"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{showPassword ? (
|
||||
<button
|
||||
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
|
||||
onClick={() => setShowPassword(false)}
|
||||
>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
|
||||
onClick={() => setShowPassword(true)}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-custom-text-400">
|
||||
You will find your access key in your Unsplash developer console.{" "}
|
||||
<a
|
||||
href="https://unsplash.com/documentation#creating-a-developer-account"
|
||||
target="_blank"
|
||||
className="text-custom-primary-100 hover:underline"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center py-1">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Saving..." : "Save changes"}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
6
god-mode/components/forms/index.ts
Normal file
6
god-mode/components/forms/index.ts
Normal file
@ -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";
|
@ -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 (
|
||||
<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 ${
|
||||
className={`flex w-full items-center justify-between gap-1 self-baseline border-t border-custom-sidebar-border-200 bg-custom-sidebar-background-100 px-4 py-2 ${
|
||||
sidebarCollapsed ? "flex-col" : ""
|
||||
}`}
|
||||
>
|
||||
|
@ -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(() => {
|
||||
<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 && (
|
||||
{!sidebarCollapsed && currentUser && (
|
||||
<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}
|
||||
<Avatar
|
||||
name={currentUser.display_name}
|
||||
src={currentUser.avatar}
|
||||
size={24}
|
||||
shape="square"
|
||||
className="!text-base"
|
||||
/> */}
|
||||
/>
|
||||
</Menu.Button>
|
||||
|
||||
<Transition
|
||||
@ -114,19 +93,20 @@ export const SidebarDropdown = observer(() => {
|
||||
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">
|
||||
<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>
|
||||
))}
|
||||
</span>
|
||||
</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={handleThemeSwitch}
|
||||
>
|
||||
<Palette className="h-4 w-4 stroke-[1.5]" />
|
||||
Switch to {resolvedTheme === "dark" ? "light" : "dark"} mode
|
||||
</Menu.Item>
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<Menu.Item
|
||||
@ -139,16 +119,6 @@ export const SidebarDropdown = observer(() => {
|
||||
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>
|
||||
|
@ -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";
|
||||
|
61
god-mode/components/toast-alert/index.tsx
Normal file
61
god-mode/components/toast-alert/index.tsx
Normal file
@ -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 (
|
||||
<div className="pointer-events-none fixed right-5 top-5 z-50 h-full w-80 space-y-5 overflow-hidden">
|
||||
{alerts.map((alert) => (
|
||||
<div className="relative overflow-hidden rounded-md text-white" key={alert.id}>
|
||||
<div className="absolute right-1 top-1">
|
||||
<button
|
||||
type="button"
|
||||
className="pointer-events-auto inline-flex rounded-md p-1.5 focus:outline-none focus:ring-2 focus:ring-offset-2"
|
||||
onClick={() => removeAlert(alert.id)}
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<X className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className={`px-2 py-4 ${
|
||||
alert.type === "success"
|
||||
? "bg-[#06d6a0]"
|
||||
: alert.type === "error"
|
||||
? "bg-[#ef476f]"
|
||||
: alert.type === "warning"
|
||||
? "bg-[#e98601]"
|
||||
: "bg-[#1B9aaa]"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-x-3">
|
||||
<div className="flex-shrink-0">
|
||||
{alert.type === "success" ? (
|
||||
<CheckCircle className="h-8 w-8" aria-hidden="true" />
|
||||
) : alert.type === "error" ? (
|
||||
<XCircle className="h-8 w-8" />
|
||||
) : alert.type === "warning" ? (
|
||||
<AlertTriangle className="h-8 w-8" aria-hidden="true" />
|
||||
) : (
|
||||
<Info className="h-8 w-8" />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold">{alert.title}</p>
|
||||
{alert.message && <p className="mt-1 text-xs">{alert.message}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToastAlerts;
|
@ -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 (
|
||||
<div className="flex h-full w-full flex-col gap-8">
|
||||
<div className="mb-2 border-b border-custom-border-100 pb-3">
|
||||
@ -11,7 +19,7 @@ export const GeneralView = () => {
|
||||
If you have a paid subscription, you will find your license key here.
|
||||
</div>
|
||||
</div>
|
||||
{/* {instance && instanceAdmins ? (
|
||||
{instance && instanceAdmins ? (
|
||||
<InstanceGeneralForm
|
||||
instance={instance}
|
||||
instanceAdmins={instanceAdmins}
|
||||
@ -24,7 +32,7 @@ export const GeneralView = () => {
|
||||
</div>
|
||||
<Loader.Item height="50px" />
|
||||
</Loader>
|
||||
)} */}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -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;
|
@ -10,3 +10,5 @@ export const useAppTheme = (): IThemeStore => {
|
||||
throw new Error("useTheme must be used within ThemeProvider");
|
||||
return context;
|
||||
};
|
||||
|
||||
export default useAppTheme;
|
9
god-mode/hooks/use-toast.tsx
Normal file
9
god-mode/hooks/use-toast.tsx
Normal file
@ -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;
|
14
god-mode/hooks/use-user.tsx
Normal file
14
god-mode/hooks/use-user.tsx
Normal file
@ -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;
|
99
god-mode/lib/toast-provider.tsx
Normal file
99
god-mode/lib/toast-provider.tsx
Normal file
@ -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<ContextType>({} 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 (
|
||||
<toastContext.Provider value={{ setToastAlert, removeAlert, alerts: state.toastAlerts }}>
|
||||
<ToastAlert />
|
||||
{children}
|
||||
</toastContext.Provider>
|
||||
);
|
||||
};
|
29
god-mode/lib/user-provider.tsx
Normal file
29
god-mode/lib/user-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 { UserStore } from "store/user.store";
|
||||
|
||||
let userStore = new UserStore();
|
||||
|
||||
export const UserContext = createContext<UserStore>(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 (
|
||||
<>
|
||||
<UserContext.Provider value={store}>
|
||||
<NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
</UserContext.Provider>
|
||||
</>
|
||||
);
|
||||
}
|
@ -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"
|
||||
},
|
||||
|
94
god-mode/services/api.service.ts
Normal file
94
god-mode/services/api.service.ts
Normal file
@ -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<any> {
|
||||
return axios({
|
||||
method: "get",
|
||||
url: this.baseURL + url,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
post(url: string, data = {}, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "post",
|
||||
url: this.baseURL + url,
|
||||
data,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
put(url: string, data = {}, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "put",
|
||||
url: this.baseURL + url,
|
||||
data,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
patch(url: string, data = {}, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "patch",
|
||||
url: this.baseURL + url,
|
||||
data,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
delete(url: string, data?: any, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "delete",
|
||||
url: this.baseURL + url,
|
||||
data: data,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
request(config = {}) {
|
||||
return axios(config);
|
||||
}
|
||||
}
|
148
god-mode/services/auth.service.ts
Normal file
148
god-mode/services/auth.service.ts
Normal file
@ -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<IEmailCheckResponse> {
|
||||
return this.post("/api/email-check/", data, { headers: {} })
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async passwordSignIn(data: IPasswordSignInData): Promise<ILoginTokenResponse> {
|
||||
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<any> {
|
||||
return this.post(`/api/forgot-password/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async setPassword(data: { password: string }): Promise<any> {
|
||||
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<ILoginTokenResponse> {
|
||||
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<ILoginTokenResponse> {
|
||||
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<ILoginTokenResponse> {
|
||||
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<any> {
|
||||
return this.post("/api/magic-generate/", data, { headers: {} })
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async magicSignIn(data: IMagicSignInData): Promise<any> {
|
||||
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<ILoginTokenResponse> {
|
||||
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<any> {
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
53
god-mode/services/instance.service.ts
Normal file
53
god-mode/services/instance.service.ts
Normal file
@ -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<IInstance> {
|
||||
return this.get("/api/instances/", { headers: {} })
|
||||
.then((response) => response.data)
|
||||
.catch((error) => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
async getInstanceAdmins(): Promise<IInstanceAdmin[]> {
|
||||
return this.get("/api/instances/admins/")
|
||||
.then((response) => response.data)
|
||||
.catch((error) => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
async updateInstanceInfo(data: Partial<IInstance>): Promise<IInstance> {
|
||||
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<IFormattedInstanceConfiguration>
|
||||
): Promise<IInstanceConfiguration[]> {
|
||||
return this.patch("/api/instances/configurations/", data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
37
god-mode/services/user.service.ts
Normal file
37
god-mode/services/user.service.ts
Normal file
@ -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<IUser> {
|
||||
return this.get("/api/users/me/")
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async currentUserInstanceAdminStatus(): Promise<IInstanceAdminStatus> {
|
||||
return this.get("/api/users/me/instance-admin/")
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
}
|
@ -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<IInstance>;
|
||||
fetchInstanceAdmins: () => Promise<IInstanceAdmin[]>;
|
||||
updateInstanceInfo: (data: Partial<IInstance>) => Promise<IInstance>;
|
||||
fetchInstanceConfigurations: () => Promise<any>;
|
||||
updateInstanceConfigurations: (
|
||||
data: Partial<IFormattedInstanceConfiguration>
|
||||
) => Promise<IInstanceConfiguration[]>;
|
||||
}
|
||||
|
||||
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<IInstance>) =>
|
||||
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<IFormattedInstanceConfiguration>
|
||||
) =>
|
||||
await this.instanceService
|
||||
.updateInstanceConfigurations(data)
|
||||
.then((response) => {
|
||||
runInAction(() => {
|
||||
this.configurations = this.configurations
|
||||
? [...this.configurations, ...response]
|
||||
: response;
|
||||
});
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
100
god-mode/store/user.store.ts
Normal file
100
god-mode/store/user.store.ts
Normal file
@ -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<IUser>;
|
||||
fetchCurrentUserInstanceAdminStatus: () => Promise<boolean>;
|
||||
|
||||
signOut: () => Promise<void>;
|
||||
}
|
||||
|
||||
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<IUser>
|
||||
*/
|
||||
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<boolean>
|
||||
*/
|
||||
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<void>
|
||||
*/
|
||||
signOut = async () =>
|
||||
await this.authService.signOut().then(() => {
|
||||
runInAction(() => {
|
||||
this.currentUser = null;
|
||||
this.isUserLoggedIn = false;
|
||||
});
|
||||
});
|
||||
}
|
@ -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}",
|
||||
|
18
yarn.lock
18
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==
|
||||
|
Loading…
Reference in New Issue
Block a user