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:
parent
306a1212cf
commit
1bb987556d
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>;
|
||||||
|
};
|
|
@ -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">
|
||||||
|
|
|
@ -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="/"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user