Impersonate UI (#4243)

* Impersonate UI

* Depracates Old admin required

* Move permission container to features

* Remove logic from dumb components

* Fix export from v2

* Fix type cast - add tab filter

* Redirect on authed pages

* Add key back

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
sean-brydon 2022-09-08 16:35:31 +01:00 committed by GitHub
parent 306a1212cf
commit 1bb987556d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 21 deletions

View File

@ -6,6 +6,7 @@ type AdminRequiredProps = {
children?: React.ReactNode; children?: React.ReactNode;
}; };
/** @deprecated use PermssionContainer instead. Will delete once V2 goes live */
export const AdminRequired: FC<AdminRequiredProps> = ({ children, as, ...rest }) => { export const AdminRequired: FC<AdminRequiredProps> = ({ children, as, ...rest }) => {
const session = useSession(); const session = useSession();

View File

@ -2,8 +2,7 @@ import { signIn } from "next-auth/react";
import { useRef } from "react"; import { useRef } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/Button"; import { TextField, Button } from "@calcom/ui/v2";
import { TextField } from "@calcom/ui/form/fields";
import Meta from "@calcom/ui/v2/core/Meta"; import Meta from "@calcom/ui/v2/core/Meta";
import { getLayout } from "@calcom/ui/v2/core/layouts/AdminLayout"; import { getLayout } from "@calcom/ui/v2/core/layouts/AdminLayout";
@ -13,7 +12,7 @@ function AdminView() {
return ( return (
<> <>
<Meta title="impersonation" description="impersonation_description" /> <Meta title="admin" description="impersonation" />
<form <form
className="mb-6 w-full sm:w-1/2" className="mb-6 w-full sm:w-1/2"
onSubmit={(e) => { onSubmit={(e) => {
@ -23,20 +22,15 @@ function AdminView() {
console.log(res); console.log(res);
}); });
}}> }}>
<TextField <div className="flex items-center space-x-2">
name="Impersonate User" <TextField
addOnLeading={ containerClassName="w-full"
<span className="inline-flex items-center rounded-l-sm border border-r-0 border-gray-300 bg-gray-50 px-3 text-sm text-gray-500"> name="Impersonate User"
{process.env.NEXT_PUBLIC_WEBSITE_URL}/ addOnLeading={<>{process.env.NEXT_PUBLIC_WEBSITE_URL}/</>}
</span> ref={usernameRef}
} hint={t("impersonate_user_tip")}
ref={usernameRef} defaultValue={undefined}
defaultValue={undefined} />
/>
<p className="mt-2 text-sm text-gray-500" id="email-description">
{t("impersonate_user_tip")}
</p>
<div className="flex justify-end py-4">
<Button type="submit">{t("impersonate")}</Button> <Button type="submit">{t("impersonate")}</Button>
</div> </div>
</form> </form>

View File

@ -0,0 +1,25 @@
import { useSession } from "next-auth/react";
import { FC, Fragment } from "react";
import { UserPermissionRole } from ".prisma/client";
type AdminRequiredProps = {
as?: keyof JSX.IntrinsicElements;
children?: React.ReactNode;
/**Not needed right now but will be useful if we ever expand our permission roles */
roleRequired?: UserPermissionRole;
};
export const PermissionContainer: FC<AdminRequiredProps> = ({
children,
as,
roleRequired = "ADMIN",
...rest
}) => {
const session = useSession();
// Admin can do everything
if (session.data?.user.role !== roleRequired || !UserPermissionRole.ADMIN) return null;
const Component = as ?? Fragment;
return <Component {...rest}>{children}</Component>;
};

View File

@ -1,12 +1,25 @@
import React, { ComponentProps } from "react"; import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import React, { ComponentProps, useEffect } from "react";
import Shell from "../Shell"; import Shell from "../Shell";
import SettingsLayout from "./SettingsLayout"; import SettingsLayout from "./SettingsLayout";
import { UserPermissionRole } from ".prisma/client";
export default function AdminLayout({ export default function AdminLayout({
children, children,
...rest ...rest
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) { }: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
const session = useSession();
const router = useRouter();
// Force redirect on component level
useEffect(() => {
if (session.data?.user.role !== UserPermissionRole.ADMIN) {
router.replace("/settings");
}
}, [session, router]);
return ( return (
<SettingsLayout {...rest}> <SettingsLayout {...rest}>
<div className="mx-auto flex max-w-4xl flex-row divide-y divide-gray-200 md:px-12"> <div className="mx-auto flex max-w-4xl flex-row divide-y divide-gray-200 md:px-12">

View File

@ -1,3 +1,4 @@
import { useSession } from "next-auth/react";
import React, { ComponentProps, useState } from "react"; import React, { ComponentProps, useState } from "react";
import { classNames } from "@calcom/lib"; import { classNames } from "@calcom/lib";
@ -7,9 +8,10 @@ import Button from "@calcom/ui/v2/core/Button";
import { Icon } from "../../../Icon"; import { Icon } from "../../../Icon";
import { useMeta } from "../Meta"; import { useMeta } from "../Meta";
import Shell from "../Shell"; import Shell from "../Shell";
import { VerticalTabItemProps } from "../navigation/tabs/VerticalTabItem";
import VerticalTabs, { VerticalTabItem } from "../navigation/tabs/VerticalTabs"; import VerticalTabs, { VerticalTabItem } from "../navigation/tabs/VerticalTabs";
const tabs = [ const tabs: VerticalTabItemProps[] = [
{ {
name: "my_account", name: "my_account",
href: "/settings/my-account", href: "/settings/my-account",
@ -64,7 +66,6 @@ const tabs = [
name: "admin", name: "admin",
href: "/settings/admin", href: "/settings/admin",
icon: Icon.FiLock, icon: Icon.FiLock,
adminRequired: true,
children: [ children: [
// //
{ name: "impersonation", href: "/settings/admin/impersonation" }, { name: "impersonation", href: "/settings/admin/impersonation" },
@ -74,9 +75,23 @@ const tabs = [
}, },
]; ];
// The following keys are assigned to admin only
const adminRequiredKeys = ["admin"];
const useTabs = () => {
const session = useSession();
const isAdmin = session.data?.user.role === "ADMIN";
// check if name is in adminRequiredKeys
return tabs.filter((tab) => {
if (isAdmin) return true;
return !adminRequiredKeys.includes(tab.name);
});
};
const SettingsSidebarContainer = ({ className = "" }) => { const SettingsSidebarContainer = ({ className = "" }) => {
const tabsWithPermissions = useTabs();
return ( return (
<VerticalTabs tabs={tabs} className={`py-3 pl-3 ${className}`}> <VerticalTabs tabs={tabsWithPermissions} className={`py-3 pl-3 ${className}`}>
<VerticalTabItem <VerticalTabItem
name="Settings" name="Settings"
href="/" href="/"