Skip to content

Commit

Permalink
feat: Instance Admin Panel: General Settings (#2792)
Browse files Browse the repository at this point in the history
  • Loading branch information
prateekshourya29 authored Nov 17, 2023
1 parent 0bb1314 commit 385d93a
Show file tree
Hide file tree
Showing 14 changed files with 396 additions and 40 deletions.
121 changes: 101 additions & 20 deletions web/components/instance/general-form.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,125 @@
import { FC } from "react";
import { useForm } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
// ui
import { Input } from "@plane/ui";
import { Button, Input, ToggleSwitch } from "@plane/ui";
// types
import { IInstance } from "types/instance";
// hooks
import useToast from "hooks/use-toast";
import { useMobxStore } from "lib/mobx/store-provider";

export interface IInstanceGeneralForm {
data: IInstance;
instance: IInstance;
}

export interface GeneralFormValues {
instance_name: string;
namespace: string | null;
is_telemetry_enabled: boolean;
}

export const InstanceGeneralForm: FC<IInstanceGeneralForm> = (props) => {
const { data } = props;

const {} = useForm<GeneralFormValues>({
const { instance } = props;
// store
const { instance: instanceStore } = useMobxStore();
// toast
const { setToastAlert } = useToast();
// form data
const {
handleSubmit,
control,
formState: { errors, isSubmitting },
} = useForm<GeneralFormValues>({
defaultValues: {
instance_name: data.instance_name,
namespace: data.namespace,
is_telemetry_enabled: data.is_telemetry_enabled,
instance_name: instance.instance_name,
is_telemetry_enabled: instance.is_telemetry_enabled,
},
});

const onSubmit = async (formData: GeneralFormValues) => {
const payload: Partial<GeneralFormValues> = { ...formData };

await instanceStore
.updateInstanceInfo(payload)
.then(() =>
setToastAlert({
title: "Success",
type: "success",
message: "Settings updated successfully",
})
)
.catch((err) => console.error(err));
};

return (
<div className="p-5">
<div className="my-2 ">
<label>Instance Name</label>
<Input name="instance_name" />
<div className="flex flex-col gap-8 m-8">
<div className="grid grid-col grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 items-center justify-between gap-8 w-full">
<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="rounded-md font-medium w-full"
/>
)}
/>
</div>

<div className="flex flex-col gap-1">
<h4 className="text-sm">Admin Email</h4>
<Input
id="primary_email"
name="primary_email"
type="email"
value={instance.primary_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="rounded-md font-medium w-full cursor-not-allowed !text-custom-text-400"
disabled
/>
</div>
</div>
<div className="my-2">
<label>Instance ID</label>
<Input name="instance_id" value={data.instance_id} disabled={true} />

<div className="flex items-center gap-8 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="my-2">
<label>Namespace</label>
<Input name="namespace" />

<div className="flex items-center py-1">
<Button variant="primary" onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
{isSubmitting ? "Saving..." : "Save Changes"}
</Button>
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions web/components/instance/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./help-section";
export * from "./sidebar-menu";
export * from "./sidebar-dropdown";
export * from "./general-form";
148 changes: 148 additions & 0 deletions web/components/instance/sidebar-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { Fragment } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import Link from "next/link";
import { Menu, Transition } from "@headlessui/react";
import { LogOut, Settings, Shield, UserCircle2 } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useToast from "hooks/use-toast";
// services
import { AuthService } from "services/auth.service";
// ui
import { Avatar } from "@plane/ui";

// Static Data
const profileLinks = (workspaceSlug: string, userId: string) => [
{
name: "View profile",
icon: UserCircle2,
link: `/${workspaceSlug}/profile/${userId}`,
},
{
name: "Settings",
icon: Settings,
link: `/${workspaceSlug}/me/profile`,
},
];

const authService = new AuthService();

export const InstanceSidebarDropdown = observer(() => {
const router = useRouter();
// store
const {
theme: { sidebarCollapsed },
workspace: { workspaceSlug },
user: { currentUser, currentUserSettings },
} = useMobxStore();
// hooks
const { setToastAlert } = useToast();

// redirect url for normal mode
const redirectWorkspaceSlug =
workspaceSlug ||
currentUserSettings?.workspace?.last_workspace_slug ||
currentUserSettings?.workspace?.fallback_workspace_slug ||
"";

const handleSignOut = async () => {
await authService
.signOut()
.then(() => {
router.push("/");
})
.catch(() =>
setToastAlert({
type: "error",
title: "Error!",
message: "Failed to sign out. Please try again.",
})
);
};

return (
<div className="flex items-center gap-x-3 gap-y-2 px-4 py-4">
<div className="w-full h-full truncate">
<div
className={`flex flex-grow items-center gap-x-2 rounded p-1 truncate ${
sidebarCollapsed ? "justify-center" : ""
}`}
>
<div className={`flex-shrink-0 `}>
<Shield className="h-6 w-6 text-custom-text-100" />
</div>

{!sidebarCollapsed && (
<h4 className="text-custom-text-100 font-medium text-base truncate">Instance Admin Settings</h4>
)}
</div>
</div>

{!sidebarCollapsed && (
<Menu as="div" className="relative flex-shrink-0">
<Menu.Button className="grid place-items-center outline-none">
<Avatar
name={currentUser?.display_name}
src={currentUser?.avatar}
size={24}
shape="square"
className="!text-base"
/>
</Menu.Button>

<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className="absolute left-0 z-20 mt-1.5 flex flex-col w-52 origin-top-left rounded-md
border border-custom-sidebar-border-200 bg-custom-sidebar-background-100 px-1 py-2 divide-y divide-custom-sidebar-border-200 shadow-lg text-xs outline-none"
>
<div className="flex flex-col gap-2.5 pb-2">
<span className="px-2 text-custom-sidebar-text-200">{currentUser?.email}</span>
{profileLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => (
<Menu.Item key={index} as="button" type="button">
<Link href={link.link}>
<a 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}
</a>
</Link>
</Menu.Item>
))}
</div>
<div className="py-2">
<Menu.Item
as="button"
type="button"
className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80"
onClick={handleSignOut}
>
<LogOut className="h-4 w-4 stroke-[1.5]" />
Sign out
</Menu.Item>
</div>

<div className="p-2 pb-0">
<Menu.Item as="button" type="button" className="w-full">
<Link href={redirectWorkspaceSlug}>
<a className="flex w-full items-center justify-center rounded px-2 py-1 text-sm font-medium text-custom-primary-100 hover:text-custom-primary-200 bg-custom-primary-10 hover:bg-custom-primary-20">
Normal Mode
</a>
</Link>
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
)}
</div>
);
});
2 changes: 1 addition & 1 deletion web/components/instance/sidebar-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const InstanceAdminSidebarMenu = () => {
const router = useRouter();

return (
<div>
<div className="h-full overflow-y-auto w-full cursor-pointer space-y-2 p-4">
{INSTANCE_ADMIN_LINKS.map((item, index) => {
const isActive = item.name === "Settings" ? router.asPath.includes(item.href) : router.asPath === item.href;

Expand Down
15 changes: 13 additions & 2 deletions web/components/workspace/sidebar-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
const {
theme: { sidebarCollapsed },
workspace: { workspaces, currentWorkspace: activeWorkspace },
user: { currentUser, updateCurrentUser },
user: { currentUser, updateCurrentUser, isUserInstanceAdmin },
} = useMobxStore();
// hooks
const { setToastAlert } = useToast();
Expand Down Expand Up @@ -286,7 +286,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
</Menu.Item>
))}
</div>
<div className="pt-2">
<div className="py-2">
<Menu.Item
as="button"
type="button"
Expand All @@ -297,6 +297,17 @@ export const WorkspaceSidebarDropdown = observer(() => {
Sign out
</Menu.Item>
</div>
{isUserInstanceAdmin && (
<div className="p-2 pb-0">
<Menu.Item as="button" type="button" className="w-full">
<Link href="/admin">
<a className="flex w-full items-center justify-center rounded px-2 py-1 text-sm font-medium text-custom-primary-100 hover:text-custom-primary-200 bg-custom-primary-10 hover:bg-custom-primary-20">
God Mode
</a>
</Link>
</Menu.Item>
</div>
)}
</Menu.Items>
</Transition>
</Menu>
Expand Down
Loading

0 comments on commit 385d93a

Please sign in to comment.