refactor: Instance admin setting and UI updates. (#2889)

* refactor: shift instance admin restriction content to seperate component.
fix: instance components export logic.

* style: fix sidebar dropdown `God Mode` icon padding.

* style: update profile settings user dropdown menu width.

* fix: update input type to `password` for Client Secret and API/ Access Key fields.

* style: update loader design for all forms.

* fix: typo

* style: ui updates.

* chore: add show/ hide button for all password fields.
This commit is contained in:
Prateek Shourya 2023-11-27 19:41:47 +05:30 committed by GitHub
parent 10c52bf89b
commit 06d3cd7e73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 255 additions and 173 deletions

View File

@ -1,4 +1,4 @@
import { FC } from "react";
import { FC, useState } from "react";
import { Controller, useForm } from "react-hook-form";
// ui
import { Button, Input } from "@plane/ui";
@ -8,6 +8,8 @@ import { IFormattedInstanceConfiguration } from "types/instance";
import useToast from "hooks/use-toast";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// icons
import { Eye, EyeOff } from "lucide-react";
export interface IInstanceAIForm {
config: IFormattedInstanceConfiguration;
@ -20,6 +22,8 @@ export interface AIFormValues {
export const InstanceAIForm: FC<IInstanceAIForm> = (props) => {
const { config } = props;
// states
const [showPassword, setShowPassword] = useState(false);
// store
const { instance: instanceStore } = useMobxStore();
// toast
@ -55,7 +59,7 @@ export const InstanceAIForm: FC<IInstanceAIForm> = (props) => {
<>
<div className="grid grid-col grid-cols-1 lg:grid-cols-3 items-center justify-between gap-x-16 gap-y-8 w-full">
<div className="flex flex-col gap-1">
<h4 className="text-sm">GPT Engine</h4>
<h4 className="text-sm">GPT_ENGINE</h4>
<Controller
control={control}
name="GPT_ENGINE"
@ -87,24 +91,41 @@ export const InstanceAIForm: FC<IInstanceAIForm> = (props) => {
</div>
<div className="flex flex-col gap-1">
<h4 className="text-sm">API Key</h4>
<Controller
control={control}
name="OPENAI_API_KEY"
render={({ field: { value, onChange, ref } }) => (
<Input
id="OPENAI_API_KEY"
name="OPENAI_API_KEY"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.OPENAI_API_KEY)}
placeholder="sk-asddassdfasdefqsdfasd23das3dasdcasd"
className="rounded-md font-medium w-full"
/>
<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="rounded-md font-medium w-full !pr-10"
/>
)}
/>
{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
@ -121,7 +142,7 @@ export const InstanceAIForm: FC<IInstanceAIForm> = (props) => {
<div className="flex items-center py-1">
<Button variant="primary" onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
{isSubmitting ? "Saving..." : "Save Changes"}
{isSubmitting ? "Saving..." : "Save changes"}
</Button>
</div>
</>

View File

@ -25,6 +25,7 @@ export interface EmailFormValues {
export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
const { config } = props;
// states
const [showPassword, setShowPassword] = useState(false);
// store
const { instance: instanceStore } = useMobxStore();
@ -145,7 +146,7 @@ export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
ref={ref}
hasError={Boolean(errors.EMAIL_HOST_PASSWORD)}
placeholder="Password"
className="rounded-md font-medium w-full"
className="rounded-md font-medium w-full !pr-10"
/>
)}
/>
@ -222,7 +223,7 @@ export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
<div className="flex items-center py-1 max-w-4xl">
<Button variant="primary" onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
{isSubmitting ? "Saving..." : "Save Changes"}
{isSubmitting ? "Saving..." : "Save changes"}
</Button>
</div>
</>

View File

@ -15,7 +15,7 @@ export interface IInstanceGeneralForm {
export interface GeneralFormValues {
instance_name: string;
is_telemetry_enabled: boolean;
// is_telemetry_enabled: boolean;
}
export const InstanceGeneralForm: FC<IInstanceGeneralForm> = (props) => {
@ -32,7 +32,7 @@ export const InstanceGeneralForm: FC<IInstanceGeneralForm> = (props) => {
} = useForm<GeneralFormValues>({
defaultValues: {
instance_name: instance.instance_name,
is_telemetry_enabled: instance.is_telemetry_enabled,
// is_telemetry_enabled: instance.is_telemetry_enabled,
},
});
@ -101,7 +101,7 @@ export const InstanceGeneralForm: FC<IInstanceGeneralForm> = (props) => {
</div>
</div>
<div className="flex items-center gap-12 pt-4">
{/* <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">
@ -115,11 +115,11 @@ export const InstanceGeneralForm: FC<IInstanceGeneralForm> = (props) => {
render={({ field: { value, onChange } }) => <ToggleSwitch value={value} onChange={onChange} size="sm" />}
/>
</div>
</div>
</div> */}
<div className="flex items-center py-1">
<Button variant="primary" size="md" onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
{isSubmitting ? "Saving..." : "Save Changes"}
<Button variant="primary" onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
{isSubmitting ? "Saving..." : "Save changes"}
</Button>
</div>
</>

View File

@ -1,4 +1,4 @@
import { FC } from "react";
import { FC, useState } from "react";
import { Controller, useForm } from "react-hook-form";
// ui
import { Button, Input } from "@plane/ui";
@ -9,7 +9,7 @@ import useToast from "hooks/use-toast";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// icons
import { Copy } from "lucide-react";
import { Copy, Eye, EyeOff } from "lucide-react";
export interface IInstanceGithubConfigForm {
config: IFormattedInstanceConfiguration;
@ -22,6 +22,8 @@ export interface GithubConfigFormValues {
export const InstanceGithubConfigForm: FC<IInstanceGithubConfigForm> = (props) => {
const { config } = props;
// states
const [showPassword, setShowPassword] = useState(false);
// store
const { instance: instanceStore } = useMobxStore();
// toast
@ -90,24 +92,42 @@ export const InstanceGithubConfigForm: FC<IInstanceGithubConfigForm> = (props) =
</p>
</div>
<div className="flex flex-col gap-1">
<h4 className="text-sm">Client Secret</h4>
<Controller
control={control}
name="GITHUB_CLIENT_SECRET"
render={({ field: { value, onChange, ref } }) => (
<Input
id="GITHUB_CLIENT_SECRET"
name="GITHUB_CLIENT_SECRET"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.GITHUB_CLIENT_SECRET)}
placeholder="9b0050f94ec1b744e32ce79ea4ffacd40d4119cb"
className="rounded-md font-medium w-full"
/>
<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="rounded-md font-medium w-full !pr-10"
/>
)}
/>
{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
@ -153,7 +173,7 @@ export const InstanceGithubConfigForm: FC<IInstanceGithubConfigForm> = (props) =
<div className="flex flex-col gap-1">
<div className="flex items-center">
<Button variant="primary" onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
{isSubmitting ? "Saving..." : "Save Changes"}
{isSubmitting ? "Saving..." : "Save changes"}
</Button>
</div>
</div>

View File

@ -90,7 +90,7 @@ export const InstanceGoogleConfigForm: FC<IInstanceGoogleConfigForm> = (props) =
</p>
</div>
<div className="flex flex-col gap-1">
<h4 className="text-sm">Origin URL</h4>
<h4 className="text-sm">JavaScript origin URL</h4>
<Button
variant="neutral-primary"
className="py-2 flex justify-between items-center"
@ -122,7 +122,7 @@ export const InstanceGoogleConfigForm: FC<IInstanceGoogleConfigForm> = (props) =
<div className="flex flex-col gap-1">
<div className="flex items-center">
<Button variant="primary" onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
{isSubmitting ? "Saving..." : "Save Changes"}
{isSubmitting ? "Saving..." : "Save changes"}
</Button>
</div>
</div>

View File

@ -1,4 +1,4 @@
import { FC } from "react";
import { FC, useState } from "react";
import { Controller, useForm } from "react-hook-form";
// ui
import { Button, Input } from "@plane/ui";
@ -8,6 +8,8 @@ import { IFormattedInstanceConfiguration } from "types/instance";
import useToast from "hooks/use-toast";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// icons
import { Eye, EyeOff } from "lucide-react";
export interface IInstanceImageConfigForm {
config: IFormattedInstanceConfiguration;
@ -19,6 +21,8 @@ export interface ImageConfigFormValues {
export const InstanceImageConfigForm: FC<IInstanceImageConfigForm> = (props) => {
const { config } = props;
// states
const [showPassword, setShowPassword] = useState(false);
// store
const { instance: instanceStore } = useMobxStore();
// toast
@ -54,23 +58,40 @@ export const InstanceImageConfigForm: FC<IInstanceImageConfigForm> = (props) =>
<div className="grid grid-col grid-cols-1 lg:grid-cols-2 items-center justify-between gap-x-16 gap-y-8 w-full">
<div className="flex flex-col gap-1 max-w-md">
<h4 className="text-sm">Access key from your Unsplash account</h4>
<Controller
control={control}
name="UNSPLASH_ACCESS_KEY"
render={({ field: { value, onChange, ref } }) => (
<Input
id="UNSPLASH_ACCESS_KEY"
name="UNSPLASH_ACCESS_KEY"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.UNSPLASH_ACCESS_KEY)}
placeholder="oXgq-sdfadsaeweqasdfasdf3234234rassd"
className="rounded-md font-medium w-full"
/>
<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="rounded-md font-medium w-full !pr-10"
/>
)}
/>
{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
@ -87,7 +108,7 @@ export const InstanceImageConfigForm: FC<IInstanceImageConfigForm> = (props) =>
<div className="flex items-center py-1">
<Button variant="primary" onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
{isSubmitting ? "Saving..." : "Save Changes"}
{isSubmitting ? "Saving..." : "Save changes"}
</Button>
</div>
</>

View File

@ -2,3 +2,9 @@ export * from "./help-section";
export * from "./sidebar-menu";
export * from "./sidebar-dropdown";
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";
export * from "./instance-admin-restriction";

View File

@ -0,0 +1,79 @@
import { FC } from "react";
import Link from "next/link";
import Image from "next/image";
// images
import AccessDeniedImg from "public/auth/access-denied.svg";
// ui
import { Button } from "@plane/ui";
// icons
import { LayoutGrid } from "lucide-react";
interface InstanceAdminRestrictionProps {
redirectWorkspaceSlug: string;
}
export const InstanceAdminRestriction: FC<InstanceAdminRestrictionProps> = ({ redirectWorkspaceSlug }) => (
<div className={`my-8 w-full flex flex-col gap-4 items-center justify-center overflow-hidden`}>
<div className="w-3/5 bg-custom-background-90">
<div className="grid h-full place-items-center p-2 pb-0">
<div className="text-center">
<Image src={AccessDeniedImg} height="250" width="550" alt="AccessDeniedImg" />
<h3 className="text-3xl font-semibold">God mode needs a god role</h3>
<p className="text-base text-custom-text-300">Doesnt look like you have that role.</p>
</div>
<div className="flex flex-col gap-2 my-8 text-center">
<div>
<p className="font-medium text-xs text-custom-text-400 tracking-tight">Do we have a god role?</p>
<p className="text-custom-text-300 text-sm">Yes.</p>
</div>
<div>
<p className="font-medium text-xs text-custom-text-400 tracking-tight">Do we call it god role?</p>
<p className="text-custom-text-300 text-sm">No. Obviously not.</p>
</div>
<div>
<p className="font-medium text-xs text-custom-text-400 tracking-tight">Can you get it?</p>
<p className="text-custom-text-300 text-sm">Maybe. Ask your god.</p>
</div>
<div>
<p className="font-medium text-xs text-custom-text-400 tracking-tight">
Are we being intentionally cryptic?
</p>
<p className="text-custom-text-300 text-sm">Yes.</p>
</div>
<div>
<p className="font-medium text-xs text-custom-text-400 tracking-tight">
Is this for the security of your workspaces?
</p>
<p className="text-custom-text-300 text-sm">Absolutely!</p>
</div>
<div>
<p className="font-medium text-xs text-custom-text-400 tracking-tight">
Are you the god here and still seeing this?
</p>
<p className="text-custom-text-300 text-sm">
Sorry, God.{" "}
<a
href="https://discord.com/channels/1031547764020084846/1094927053867995176"
target="_blank"
className="text-custom-primary-100 font-medium hover:underline"
rel="noreferrer"
>
Talk to us.
</a>
</p>
</div>
</div>
</div>
</div>
<div className="flex items-center justify-center gap-2">
<Link href={`/${redirectWorkspaceSlug}`}>
<a>
<Button variant="primary" size="sm">
<LayoutGrid width={16} height={16} />
To the workspace
</Button>
</a>
</Link>
</div>
</div>
);

View File

@ -7,7 +7,7 @@ import { mutate } from "swr";
// components
import { Menu, Transition } from "@headlessui/react";
// icons
import { Cog, LogIn, LogOut, Settings } from "lucide-react";
import { LogIn, LogOut, Settings, UserCog2 } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
@ -64,12 +64,12 @@ export const InstanceSidebarDropdown = observer(() => {
<div className="flex items-center gap-x-5 gap-y-2 px-4 py-3.5 max-h-[3.75rem] border-b border-custom-sidebar-border-200">
<div className="w-full h-full truncate">
<div
className={`flex flex-grow items-center gap-x-2 rounded p-1 truncate ${
className={`flex flex-grow items-center gap-x-2 rounded py-1 truncate ${
sidebarCollapsed ? "justify-center" : ""
}`}
>
<div className="flex-shrink-0 flex items-center justify-center h-7 w-7 rounded bg-custom-sidebar-background-80">
<Cog className="h-5 w-5 text-custom-text-200" />
<div className="flex-shrink-0 flex items-center justify-center h-7 w-7 rounded bg-custom-sidebar-background-80">
<UserCog2 className="h-5 w-5 text-custom-text-200" />
</div>
{!sidebarCollapsed && (

View File

@ -16,9 +16,9 @@ const INSTANCE_ADMIN_LINKS = [
},
{
Icon: Mail,
name: "Mail",
name: "Email",
description: "Set up emails to your users",
href: `/god-mode/mail`,
href: `/god-mode/email`,
},
{
Icon: Lock,
@ -28,7 +28,7 @@ const INSTANCE_ADMIN_LINKS = [
},
{
Icon: BrainCog,
name: "OpenAI",
name: "Artificial intelligence",
description: "Configure your OpenAI creds",
href: `/god-mode/ai`,
},

View File

@ -1,15 +1,9 @@
import { FC, ReactNode } from "react";
import Link from "next/link";
import Image from "next/image";
import { observer } from "mobx-react-lite";
// icons
import { LayoutGrid } from "lucide-react";
// ui
import { Button } from "@plane/ui";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
// images
import AccessDeniedImg from "public/auth/access-denied.svg";
// components
import { InstanceAdminRestriction } from "components/instance";
export interface IAdminAuthWrapper {
children: ReactNode;
@ -32,71 +26,7 @@ export const AdminAuthWrapper: FC<IAdminAuthWrapper> = observer(({ children }) =
// if user does not have admin access to the instance
if (isUserInstanceAdmin !== undefined && isUserInstanceAdmin === false) {
return (
<div className={`my-8 w-full flex flex-col gap-4 items-center justify-center overflow-hidden`}>
<div className="w-3/5 bg-custom-background-90">
<div className="grid h-full place-items-center p-2 pb-0">
<div className="text-center">
<Image src={AccessDeniedImg} height="250" width="550" alt="AccessDeniedImg" />
<h3 className="text-3xl font-semibold">God mode needs a god role</h3>
<p className="text-base text-custom-text-300">Doesnt look like you have that role.</p>
</div>
<div className="flex flex-col gap-2 my-8 text-center">
<div>
<p className="font-medium text-xs text-custom-text-400 tracking-tight">Do we have a god role?</p>
<p className="text-custom-text-300 text-sm">Yes.</p>
</div>
<div>
<p className="font-medium text-xs text-custom-text-400 tracking-tight">Do we call it god role?</p>
<p className="text-custom-text-300 text-sm">No. Obviously not.</p>
</div>
<div>
<p className="font-medium text-xs text-custom-text-400 tracking-tight">Can you get it?</p>
<p className="text-custom-text-300 text-sm">Maybe. Ask your god.</p>
</div>
<div>
<p className="font-medium text-xs text-custom-text-400 tracking-tight">
Are we being intentionally cryptic?
</p>
<p className="text-custom-text-300 text-sm">Yes.</p>
</div>
<div>
<p className="font-medium text-xs text-custom-text-400 tracking-tight">
Is this for the security of your workspaces?
</p>
<p className="text-custom-text-300 text-sm">Absolutely!</p>
</div>
<div>
<p className="font-medium text-xs text-custom-text-400 tracking-tight">
Are you the god here and still seeing this?
</p>
<p className="text-custom-text-300 text-sm">
Sorry, God.{" "}
<a
href="https://discord.com/channels/1031547764020084846/1094927053867995176"
target="_blank"
className="text-custom-primary-100 font-medium hover:underline"
rel="noreferrer"
>
Talk to us.
</a>
</p>
</div>
</div>
</div>
</div>
<div className="flex items-center justify-center gap-2">
<Link href={`/${redirectWorkspaceSlug}`}>
<a>
<Button variant="primary" size="sm">
<LayoutGrid width={16} height={16} />
To the workspace
</Button>
</a>
</Link>
</div>
</div>
);
return <InstanceAdminRestriction redirectWorkspaceSlug={redirectWorkspaceSlug} />;
}
return <>{children}</>;

View File

@ -119,7 +119,7 @@ export const ProfileLayoutSidebar = observer(() => {
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute left-0 z-20 mt-1 rounded-md border border-custom-sidebar-border-200 bg-custom-sidebar-background-100 px-1 py-2 shadow-custom-shadow-rg text-xs space-y-2 outline-none">
<Menu.Items className="absolute left-0 z-20 mt-1 w-52 rounded-md border border-custom-sidebar-border-200 bg-custom-sidebar-background-100 px-1 py-2 shadow-custom-shadow-rg text-xs space-y-2 outline-none">
<span className="px-2 text-custom-sidebar-text-200">{currentUser?.email}</span>
<Menu.Item
as="button"

View File

@ -12,7 +12,7 @@ import { Loader } from "@plane/ui";
// icons
import { Lightbulb } from "lucide-react";
// components
import { InstanceAIForm } from "components/instance/ai-form";
import { InstanceAIForm } from "components/instance";
const InstanceAdminAIPage: NextPageWithLayout = observer(() => {
// store
@ -46,9 +46,11 @@ const InstanceAdminAIPage: NextPageWithLayout = observer(() => {
</>
) : (
<Loader className="space-y-4">
<Loader.Item height="50px" width="50%" />
<Loader.Item height="50px" width="50%" />
<Loader.Item height="50px" width="25%" />
<div className="grid grid-cols-2 gap-y-4 gap-x-8">
<Loader.Item height="50px" />
<Loader.Item height="50px" />
</div>
<Loader.Item height="50px" />
</Loader>
)}
</div>

View File

@ -13,8 +13,7 @@ import useToast from "hooks/use-toast";
// ui
import { Loader, ToggleSwitch } from "@plane/ui";
// components
import { InstanceGoogleConfigForm } from "components/instance/google-config-form";
import { InstanceGithubConfigForm } from "components/instance/github-config-form";
import { InstanceGithubConfigForm, InstanceGoogleConfigForm } from "components/instance";
const InstanceAdminAuthorizationPage: NextPageWithLayout = observer(() => {
// store
@ -168,13 +167,11 @@ const InstanceAdminAuthorizationPage: NextPageWithLayout = observer(() => {
</>
) : (
<Loader className="space-y-4">
<Loader.Item height="50px" width="50%" />
<Loader.Item height="50px" width="50%" />
<Loader.Item height="50px" width="50%" />
<div className="grid grid-cols-2 gap-y-4 gap-x-8">
<Loader.Item height="50px" />
<Loader.Item height="50px" />
</div>
<Loader.Item height="50px" />
<Loader.Item height="50px" width="25%" />
<Loader.Item height="50px" />
<Loader.Item height="50px" width="25%" />
</Loader>
)}
</div>

View File

@ -10,9 +10,9 @@ import { useMobxStore } from "lib/mobx/store-provider";
// ui
import { Loader } from "@plane/ui";
// components
import { InstanceEmailForm } from "components/instance/email-form";
import { InstanceEmailForm } from "components/instance";
const InstanceAdminMailPage: NextPageWithLayout = observer(() => {
const InstanceAdminEmailPage: NextPageWithLayout = observer(() => {
// store
const {
instance: { fetchInstanceConfigurations, formattedConfig },
@ -36,19 +36,19 @@ const InstanceAdminMailPage: NextPageWithLayout = observer(() => {
<InstanceEmailForm config={formattedConfig} />
) : (
<Loader className="space-y-4">
<div className="grid grid-cols-2 gap-y-4 gap-x-8">
<Loader.Item height="50px" />
<Loader.Item height="50px" />
</div>
<Loader.Item height="50px" />
<Loader.Item height="50px" />
<Loader.Item height="50px" width="25%" />
<Loader.Item height="50px" width="25%" />
<Loader.Item height="50px" width="25%" />
</Loader>
)}
</div>
);
});
InstanceAdminMailPage.getLayout = function getLayout(page: ReactElement) {
InstanceAdminEmailPage.getLayout = function getLayout(page: ReactElement) {
return <InstanceAdminLayout>{page}</InstanceAdminLayout>;
};
export default InstanceAdminMailPage;
export default InstanceAdminEmailPage;

View File

@ -10,7 +10,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
// ui
import { Loader } from "@plane/ui";
// components
import { InstanceImageConfigForm } from "components/instance/image-config-form";
import { InstanceImageConfigForm } from "components/instance";
const InstanceAdminImagePage: NextPageWithLayout = observer(() => {
// store
@ -32,8 +32,11 @@ const InstanceAdminImagePage: NextPageWithLayout = observer(() => {
<InstanceImageConfigForm config={formattedConfig} />
) : (
<Loader className="space-y-4">
<div className="grid grid-cols-2 gap-y-4 gap-x-8">
<Loader.Item height="50px" />
<Loader.Item height="50px" />
</div>
<Loader.Item height="50px" />
<Loader.Item height="50px" width="25%" />
</Loader>
)}
</div>

View File

@ -34,9 +34,11 @@ const InstanceAdminPage: NextPageWithLayout = observer(() => {
<InstanceGeneralForm instance={instance} instanceAdmins={instanceAdmins} />
) : (
<Loader className="space-y-4">
<div className="grid grid-cols-2 gap-y-4 gap-x-8">
<Loader.Item height="50px" />
<Loader.Item height="50px" />
</div>
<Loader.Item height="50px" />
<Loader.Item height="50px" width="50%" />
<Loader.Item height="50px" width="25%" />
</Loader>
)}
</div>