feat(ui): changed back to relative time distance with tooltip of detailed time

This commit is contained in:
Akhil Mohan
2024-03-29 22:36:46 +05:30
parent 31ad6b0c86
commit 00f2d40803
6 changed files with 184 additions and 106 deletions
@@ -49,6 +49,7 @@ const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2;
const formatRoleName = (role: string, customRoleName?: string) => { const formatRoleName = (role: string, customRoleName?: string) => {
if (role === ProjectMembershipRole.Custom) return customRoleName; if (role === ProjectMembershipRole.Custom) return customRoleName;
if (role === ProjectMembershipRole.Member) return "Developer"; if (role === ProjectMembershipRole.Member) return "Developer";
if (role === ProjectMembershipRole.NoAccess) return "No access";
return role; return role;
}; };
export const IdentityTab = withProjectPermission( export const IdentityTab = withProjectPermission(
@@ -173,7 +174,9 @@ export const IdentityTab = withProjectPermission(
return ( return (
<Tag key={roleId}> <Tag key={roleId}>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div>{formatRoleName(role, customRoleName)}</div> <div className="capitalize">
{formatRoleName(role, customRoleName)}
</div>
{isTemporary && ( {isTemporary && (
<div> <div>
<Tooltip <Tooltip
@@ -1,8 +1,9 @@
/* eslint-disable no-nested-ternary */
import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faCaretDown, faClock, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; import { faCaretDown, faClock, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { format } from "date-fns"; import { format, formatDistance } from "date-fns";
import ms from "ms"; import ms from "ms";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { z } from "zod"; import { z } from "zod";
@@ -21,7 +22,8 @@ import {
Select, Select,
SelectItem, SelectItem,
Spinner, Spinner,
Tag Tag,
Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
@@ -182,24 +184,41 @@ export const IdentityRbacSection = ({ identityProjectMember, onOpenUpgradeModal
/> />
<Popover> <Popover>
<PopoverTrigger disabled={isMemberEditDisabled}> <PopoverTrigger disabled={isMemberEditDisabled}>
<div>
<Tooltip
content={
temporaryAccess?.isTemporary
? isExpired
? "Timed Access Expired"
: `Until ${format(
new Date(temporaryAccess.temporaryAccessEndTime || ""),
"yyyy-MM-dd HH:mm:ss"
)}`
: "Non expiry access"
}
>
<Button <Button
variant="outline_bg" variant="outline_bg"
leftIcon={isTemporary ? <FontAwesomeIcon icon={faClock} /> : undefined} leftIcon={isTemporary ? <FontAwesomeIcon icon={faClock} /> : undefined}
rightIcon={<FontAwesomeIcon icon={faCaretDown} className="ml-2" />} rightIcon={<FontAwesomeIcon icon={faCaretDown} className="ml-2" />}
isDisabled={isMemberEditDisabled} isDisabled={isMemberEditDisabled}
className={twMerge( className={twMerge(
"border-none bg-mineshaft-600 hover:bg-mineshaft-500 py-2.5 capitalize text-xs", "border-none bg-mineshaft-600 py-2.5 text-xs capitalize hover:bg-mineshaft-500",
isTemporary && "text-primary", isTemporary && "text-primary",
isExpired && "text-red-600" isExpired && "text-red-600"
)} )}
> >
{!temporaryAccess?.isTemporary {temporaryAccess?.isTemporary
? "Permanent" ? isExpired
: `Expires at ${format( ? "Access Expired"
: formatDistance(
new Date(temporaryAccess.temporaryAccessEndTime || ""), new Date(temporaryAccess.temporaryAccessEndTime || ""),
"yyyy-MM-dd HH:mm:ss" new Date()
)}`} )
: "Permanent"}
</Button> </Button>
</Tooltip>
</div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent
arrowClassName="fill-gray-600" arrowClassName="fill-gray-600"
@@ -279,7 +298,7 @@ export const IdentityRbacSection = ({ identityProjectMember, onOpenUpgradeModal
</Popover> </Popover>
<IconButton <IconButton
variant="outline_bg" variant="outline_bg"
className="border border-mineshaft-500 bg-mineshaft-600 hover:bg-red/20 hover:border-red/70 py-3" className="border border-mineshaft-500 bg-mineshaft-600 py-3 hover:border-red/70 hover:bg-red/20"
ariaLabel="delete-role" ariaLabel="delete-role"
isDisabled={isMemberEditDisabled} isDisabled={isMemberEditDisabled}
onClick={() => { onClick={() => {
@@ -1,8 +1,15 @@
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { faArrowRotateLeft, faCaretDown, faCheck, faClock, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; import {
faArrowRotateLeft,
faCaretDown,
faCheck,
faClock,
faPlus,
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { format } from "date-fns"; import { format, formatDistance } from "date-fns";
import ms from "ms"; import ms from "ms";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { z } from "zod"; import { z } from "zod";
@@ -173,10 +180,15 @@ const SpecificPrivilegeSecretForm = ({
} }
}; };
const getAccessLabel = () => { const getAccessLabel = (exactTime = false) => {
if (isExpired) return "Access expired"; if (isExpired) return "Access expired";
if (!temporaryAccessField?.isTemporary) return "Permanent"; if (!temporaryAccessField?.isTemporary) return "Permanent";
return `Until ${format(new Date(temporaryAccessField.temporaryAccessEndTime || ""), "yyyy-MM-dd HH:mm:ss")}`; if (exactTime)
return `Until ${format(
new Date(temporaryAccessField.temporaryAccessEndTime || ""),
"yyyy-MM-dd HH:mm:ss"
)}`;
return formatDistance(new Date(temporaryAccessField.temporaryAccessEndTime || ""), new Date());
}; };
return ( return (
@@ -281,17 +293,15 @@ const SpecificPrivilegeSecretForm = ({
<div className="mt-7 flex items-center space-x-2"> <div className="mt-7 flex items-center space-x-2">
<Popover> <Popover>
<PopoverTrigger disabled={isMemberEditDisabled}> <PopoverTrigger disabled={isMemberEditDisabled}>
<Tooltip <div>
asChild <Tooltip content={getAccessLabel(true)}>
content={isExpired ? "Timed access expired" : "Grant timed access"}
>
<Button <Button
variant="outline_bg" variant="outline_bg"
isDisabled={isMemberEditDisabled}
leftIcon={isTemporary ? <FontAwesomeIcon icon={faClock} /> : undefined} leftIcon={isTemporary ? <FontAwesomeIcon icon={faClock} /> : undefined}
rightIcon={<FontAwesomeIcon icon={faCaretDown} className="ml-2" />} rightIcon={<FontAwesomeIcon icon={faCaretDown} className="ml-2" />}
isDisabled={isMemberEditDisabled}
className={twMerge( className={twMerge(
"border-none py-2.5 capitalize text-xs hover:bg-mineshaft-500", "border-none bg-mineshaft-600 py-2.5 text-xs capitalize hover:bg-mineshaft-500",
isTemporary && "text-primary", isTemporary && "text-primary",
isExpired && "text-red-600" isExpired && "text-red-600"
)} )}
@@ -299,6 +309,7 @@ const SpecificPrivilegeSecretForm = ({
{getAccessLabel()} {getAccessLabel()}
</Button> </Button>
</Tooltip> </Tooltip>
</div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent
arrowClassName="fill-gray-600" arrowClassName="fill-gray-600"
@@ -381,7 +392,7 @@ const SpecificPrivilegeSecretForm = ({
<Tooltip content="Cancel" className="mr-4"> <Tooltip content="Cancel" className="mr-4">
<IconButton <IconButton
variant="outline_bg" variant="outline_bg"
className="border border-mineshaft-500 bg-mineshaft-600 hover:bg-red/20 hover:border-red/70 py-2.5" className="border border-mineshaft-500 bg-mineshaft-600 py-2.5 hover:border-red/70 hover:bg-red/20"
ariaLabel="delete-privilege" ariaLabel="delete-privilege"
isDisabled={privilegeForm.formState.isSubmitting} isDisabled={privilegeForm.formState.isSubmitting}
onClick={() => privilegeForm.reset()} onClick={() => privilegeForm.reset()}
@@ -389,7 +400,10 @@ const SpecificPrivilegeSecretForm = ({
<FontAwesomeIcon icon={faArrowRotateLeft} className="py-0.5" /> <FontAwesomeIcon icon={faArrowRotateLeft} className="py-0.5" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<Tooltip content={isMemberEditDisabled ? "Access restricted" : "Save"} className="mr-4"> <Tooltip
content={isMemberEditDisabled ? "Access restricted" : "Save"}
className="mr-4"
>
<IconButton <IconButton
isDisabled={isMemberEditDisabled} isDisabled={isMemberEditDisabled}
className="border-none py-3" className="border-none py-3"
@@ -397,19 +411,22 @@ const SpecificPrivilegeSecretForm = ({
type="submit" type="submit"
> >
{privilegeForm.formState.isSubmitting ? ( {privilegeForm.formState.isSubmitting ? (
<Spinner size="xs" className="m-0 text-slate-500 w-3 h-3" /> <Spinner size="xs" className="m-0 h-3 w-3 text-slate-500" />
) : ( ) : (
<FontAwesomeIcon icon={faCheck} className="px-0.5"/> <FontAwesomeIcon icon={faCheck} className="px-0.5" />
)} )}
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</> </>
) : ( ) : (
<Tooltip content={isMemberEditDisabled ? "Access restricted" : "Delete"} className="mr-4"> <Tooltip
content={isMemberEditDisabled ? "Access restricted" : "Delete"}
className="mr-4"
>
<IconButton <IconButton
isDisabled={isMemberEditDisabled} isDisabled={isMemberEditDisabled}
variant="outline_bg" variant="outline_bg"
className="border border-mineshaft-500 bg-mineshaft-600 hover:bg-red/20 hover:border-red/70 py-3" className="border border-mineshaft-500 bg-mineshaft-600 py-3 hover:border-red/70 hover:bg-red/20"
ariaLabel="delete-privilege" ariaLabel="delete-privilege"
onClick={() => handlePopUpOpen("deletePrivilege")} onClick={() => handlePopUpOpen("deletePrivilege")}
> >
@@ -480,7 +497,7 @@ export const SpecificPrivilegeSection = ({ identityId }: Props) => {
Additional Privileges Additional Privileges
{isLoading && <Spinner size="xs" />} {isLoading && <Spinner size="xs" />}
</div> </div>
<p className="text-sm text-mineshaft-400 mt-0.5"> <p className="mt-0.5 text-sm text-mineshaft-400">
Select individual privileges to associate with the identity. Select individual privileges to associate with the identity.
</p> </p>
<div> <div>
@@ -76,6 +76,7 @@ const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2;
const formatRoleName = (role: string, customRoleName?: string) => { const formatRoleName = (role: string, customRoleName?: string) => {
if (role === ProjectMembershipRole.Custom) return customRoleName; if (role === ProjectMembershipRole.Custom) return customRoleName;
if (role === ProjectMembershipRole.Member) return "Developer"; if (role === ProjectMembershipRole.Member) return "Developer";
if (role === ProjectMembershipRole.NoAccess) return "No access";
return role; return role;
}; };
@@ -279,7 +280,9 @@ export const MemberListTab = () => {
return ( return (
<Tag key={id}> <Tag key={id}>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div>{formatRoleName(role, customRoleName)}</div> <div className="capitalize">
{formatRoleName(role, customRoleName)}
</div>
{isTemporary && ( {isTemporary && (
<div> <div>
<Tooltip <Tooltip
@@ -1,8 +1,9 @@
/* eslint-disable no-nested-ternary */
import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faCaretDown, faClock, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; import { faCaretDown, faClock, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { format } from "date-fns"; import { format, formatDistance } from "date-fns";
import ms from "ms"; import ms from "ms";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { z } from "zod"; import { z } from "zod";
@@ -21,7 +22,8 @@ import {
Select, Select,
SelectItem, SelectItem,
Spinner, Spinner,
Tag Tag,
Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
@@ -170,7 +172,7 @@ export const MemberRbacSection = ({ projectMember, onOpenUpgradeModal }: Props)
{...field} {...field}
isDisabled={isMemberEditDisabled} isDisabled={isMemberEditDisabled}
onValueChange={(e) => onChange(e)} onValueChange={(e) => onChange(e)}
className="w-full bg-mineshaft-600 hover:bg-mineshaft-500 duration-200" className="w-full bg-mineshaft-600 duration-200 hover:bg-mineshaft-500"
> >
{projectRoles?.map(({ name, slug, id: projectRoleId }) => ( {projectRoles?.map(({ name, slug, id: projectRoleId }) => (
<SelectItem value={slug} key={projectRoleId}> <SelectItem value={slug} key={projectRoleId}>
@@ -181,25 +183,42 @@ export const MemberRbacSection = ({ projectMember, onOpenUpgradeModal }: Props)
)} )}
/> />
<Popover> <Popover>
<PopoverTrigger disabled={isMemberEditDisabled}> <PopoverTrigger disabled={isMemberEditDisabled} asChild>
<div>
<Tooltip
content={
temporaryAccess?.isTemporary
? isExpired
? "Timed Access Expired"
: `Until ${format(
new Date(temporaryAccess.temporaryAccessEndTime || ""),
"yyyy-MM-dd HH:mm:ss"
)}`
: "Non expiry access"
}
>
<Button <Button
variant="outline_bg" variant="outline_bg"
leftIcon={isTemporary ? <FontAwesomeIcon icon={faClock} /> : undefined} leftIcon={isTemporary ? <FontAwesomeIcon icon={faClock} /> : undefined}
rightIcon={<FontAwesomeIcon icon={faCaretDown} className="ml-2" />} rightIcon={<FontAwesomeIcon icon={faCaretDown} className="ml-2" />}
isDisabled={isMemberEditDisabled} isDisabled={isMemberEditDisabled}
className={twMerge( className={twMerge(
"border-none bg-mineshaft-600 hover:bg-mineshaft-500 py-2.5 capitalize text-xs", "border-none bg-mineshaft-600 py-2.5 text-xs capitalize hover:bg-mineshaft-500",
isTemporary && "text-primary", isTemporary && "text-primary",
isExpired && "text-red-600" isExpired && "text-red-600"
)} )}
> >
{!temporaryAccess?.isTemporary {temporaryAccess?.isTemporary
? "Permanent" ? isExpired
: `Until ${format( ? "Access Expired"
: formatDistance(
new Date(temporaryAccess.temporaryAccessEndTime || ""), new Date(temporaryAccess.temporaryAccessEndTime || ""),
"yyyy-MM-dd HH:mm:ss" new Date()
)}`} )
: "Permanent"}
</Button> </Button>
</Tooltip>
</div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent
arrowClassName="fill-gray-600" arrowClassName="fill-gray-600"
@@ -279,7 +298,7 @@ export const MemberRbacSection = ({ projectMember, onOpenUpgradeModal }: Props)
</Popover> </Popover>
<IconButton <IconButton
variant="outline_bg" variant="outline_bg"
className="border border-mineshaft-500 bg-mineshaft-600 hover:bg-red/20 hover:border-red/70 py-3" className="border border-mineshaft-500 bg-mineshaft-600 py-3 hover:border-red/70 hover:bg-red/20"
ariaLabel="delete-role" ariaLabel="delete-role"
isDisabled={isMemberEditDisabled} isDisabled={isMemberEditDisabled}
onClick={() => { onClick={() => {
@@ -1,8 +1,15 @@
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { faArrowRotateLeft, faCaretDown, faCheck, faClock, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; import {
faArrowRotateLeft,
faCaretDown,
faCheck,
faClock,
faPlus,
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { format } from "date-fns"; import { format, formatDistance } from "date-fns";
import ms from "ms"; import ms from "ms";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { z } from "zod"; import { z } from "zod";
@@ -161,10 +168,15 @@ const SpecificPrivilegeSecretForm = ({ privilege }: { privilege: TProjectUserPri
} }
}; };
const getAccessLabel = () => { const getAccessLabel = (exactTime = false) => {
if (isExpired) return "Access expired"; if (isExpired) return "Access expired";
if (!temporaryAccessField?.isTemporary) return "Permanent"; if (!temporaryAccessField?.isTemporary) return "Permanent";
return `Until ${format(new Date(temporaryAccessField.temporaryAccessEndTime || ""), "yyyy-MM-dd HH:mm:ss")}`; if (exactTime)
return `Until ${format(
new Date(temporaryAccessField.temporaryAccessEndTime || ""),
"yyyy-MM-dd HH:mm:ss"
)}`;
return formatDistance(new Date(temporaryAccessField.temporaryAccessEndTime || ""), new Date());
}; };
return ( return (
@@ -269,17 +281,15 @@ const SpecificPrivilegeSecretForm = ({ privilege }: { privilege: TProjectUserPri
<div className="mt-7 flex items-center space-x-2"> <div className="mt-7 flex items-center space-x-2">
<Popover> <Popover>
<PopoverTrigger disabled={isMemberEditDisabled}> <PopoverTrigger disabled={isMemberEditDisabled}>
<Tooltip <div>
asChild <Tooltip content={getAccessLabel(true)}>
content={isExpired ? "Timed access expired" : "Grant timed access"}
>
<Button <Button
variant="outline_bg" variant="outline_bg"
isDisabled={isMemberEditDisabled}
leftIcon={isTemporary ? <FontAwesomeIcon icon={faClock} /> : undefined} leftIcon={isTemporary ? <FontAwesomeIcon icon={faClock} /> : undefined}
rightIcon={<FontAwesomeIcon icon={faCaretDown} className="ml-2" />} rightIcon={<FontAwesomeIcon icon={faCaretDown} className="ml-2" />}
isDisabled={isMemberEditDisabled}
className={twMerge( className={twMerge(
"border-none py-2.5 capitalize text-xs hover:bg-mineshaft-500", "border-none bg-mineshaft-600 py-2.5 text-xs capitalize hover:bg-mineshaft-500",
isTemporary && "text-primary", isTemporary && "text-primary",
isExpired && "text-red-600" isExpired && "text-red-600"
)} )}
@@ -287,6 +297,7 @@ const SpecificPrivilegeSecretForm = ({ privilege }: { privilege: TProjectUserPri
{getAccessLabel()} {getAccessLabel()}
</Button> </Button>
</Tooltip> </Tooltip>
</div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent
arrowClassName="fill-gray-600" arrowClassName="fill-gray-600"
@@ -369,7 +380,7 @@ const SpecificPrivilegeSecretForm = ({ privilege }: { privilege: TProjectUserPri
<Tooltip content="Cancel" className="mr-4"> <Tooltip content="Cancel" className="mr-4">
<IconButton <IconButton
variant="outline_bg" variant="outline_bg"
className="border border-mineshaft-500 bg-mineshaft-600 hover:bg-red/20 hover:border-red/70 py-2.5" className="border border-mineshaft-500 bg-mineshaft-600 py-2.5 hover:border-red/70 hover:bg-red/20"
ariaLabel="delete-privilege" ariaLabel="delete-privilege"
isDisabled={privilegeForm.formState.isSubmitting} isDisabled={privilegeForm.formState.isSubmitting}
onClick={() => privilegeForm.reset()} onClick={() => privilegeForm.reset()}
@@ -377,7 +388,10 @@ const SpecificPrivilegeSecretForm = ({ privilege }: { privilege: TProjectUserPri
<FontAwesomeIcon icon={faArrowRotateLeft} className="py-0.5" /> <FontAwesomeIcon icon={faArrowRotateLeft} className="py-0.5" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<Tooltip content={isMemberEditDisabled ? "Access restricted" : "Save"} className="mr-4"> <Tooltip
content={isMemberEditDisabled ? "Access restricted" : "Save"}
className="mr-4"
>
<IconButton <IconButton
isDisabled={isMemberEditDisabled} isDisabled={isMemberEditDisabled}
className="border-none py-3" className="border-none py-3"
@@ -385,19 +399,22 @@ const SpecificPrivilegeSecretForm = ({ privilege }: { privilege: TProjectUserPri
type="submit" type="submit"
> >
{privilegeForm.formState.isSubmitting ? ( {privilegeForm.formState.isSubmitting ? (
<Spinner size="xs" className="m-0 text-slate-500 w-3 h-3" /> <Spinner size="xs" className="m-0 h-3 w-3 text-slate-500" />
) : ( ) : (
<FontAwesomeIcon icon={faCheck} className="px-0.5"/> <FontAwesomeIcon icon={faCheck} className="px-0.5" />
)} )}
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</> </>
) : ( ) : (
<Tooltip content={isMemberEditDisabled ? "Access restricted" : "Delete"} className="mr-4"> <Tooltip
content={isMemberEditDisabled ? "Access restricted" : "Delete"}
className="mr-4"
>
<IconButton <IconButton
isDisabled={isMemberEditDisabled} isDisabled={isMemberEditDisabled}
variant="outline_bg" variant="outline_bg"
className="border border-mineshaft-500 bg-mineshaft-600 hover:bg-red/20 hover:border-red/70 py-3" className="border border-mineshaft-500 bg-mineshaft-600 py-3 hover:border-red/70 hover:bg-red/20"
ariaLabel="delete-privilege" ariaLabel="delete-privilege"
onClick={() => handlePopUpOpen("deletePrivilege")} onClick={() => handlePopUpOpen("deletePrivilege")}
> >
@@ -463,7 +480,7 @@ export const SpecificPrivilegeSection = ({ membershipId }: Props) => {
Additional Privileges Additional Privileges
{isLoading && <Spinner size="xs" />} {isLoading && <Spinner size="xs" />}
</div> </div>
<p className="text-sm text-mineshaft-400 mt-0.5"> <p className="mt-0.5 text-sm text-mineshaft-400">
Select individual privileges to associate with the user. Select individual privileges to associate with the user.
</p> </p>
<div> <div>