improvement: minor UI adjustments to make project access control tabs more uniform

This commit is contained in:
Scott Wilson
2025-06-16 17:17:55 -07:00
parent a89bd08c08
commit 0c5155f8e6
6 changed files with 137 additions and 100 deletions
@@ -72,7 +72,7 @@ export const GroupsSection = () => {
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Groups}> <ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Groups}>
{(isAllowed) => ( {(isAllowed) => (
<Button <Button
colorSchema="primary" colorSchema="secondary"
type="submit" type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handleAddGroupModal()} onClick={() => handleAddGroupModal()}
@@ -2,10 +2,11 @@ import { useMemo } from "react";
import { import {
faArrowDown, faArrowDown,
faArrowUp, faArrowUp,
faEllipsisV,
faMagnifyingGlass, faMagnifyingGlass,
faSearch, faSearch,
faTrash, faUsers,
faUsers faUsersSlash
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router";
@@ -13,6 +14,10 @@ import { format } from "date-fns";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
EmptyState, EmptyState,
IconButton, IconButton,
Input, Input,
@@ -184,14 +189,27 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => {
</Td> </Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td> <Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
<Td className="flex justify-end"> <Td className="flex justify-end">
<Tooltip className="max-w-sm text-center" content="Options">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<IconButton
ariaLabel="Options"
colorSchema="secondary"
className="w-6"
variant="plain"
>
<FontAwesomeIcon icon={faEllipsisV} />
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent sideOffset={2} align="end">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.Groups} a={ProjectPermissionSub.Groups}
> >
{(isAllowed) => ( {(isAllowed) => (
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100"> <DropdownMenuItem
<Tooltip content="Remove"> icon={<FontAwesomeIcon icon={faUsersSlash} />}
<IconButton isDisabled={!isAllowed}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handlePopUpOpen("deleteGroup", { handlePopUpOpen("deleteGroup", {
@@ -199,18 +217,14 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => {
name name
}); });
}} }}
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
> >
<FontAwesomeIcon icon={faTrash} /> Remove Group From Project
</IconButton> </DropdownMenuItem>
</Tooltip>
</div>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</Tooltip>
</Td> </Td>
</Tr> </Tr>
); );
@@ -3,12 +3,13 @@ import {
faArrowDown, faArrowDown,
faArrowUp, faArrowUp,
faArrowUpRightFromSquare, faArrowUpRightFromSquare,
faBookOpen,
faCircleMinus,
faClock, faClock,
faEllipsisV, faEllipsisV,
faMagnifyingGlass, faMagnifyingGlass,
faPlus, faPlus,
faServer, faServer
faXmark
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router";
@@ -21,6 +22,10 @@ import { ProjectPermissionCan } from "@app/components/permissions";
import { import {
Button, Button,
DeleteActionModal, DeleteActionModal,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
EmptyState, EmptyState,
HoverCard, HoverCard,
HoverCardContent, HoverCardContent,
@@ -163,20 +168,21 @@ export const IdentityTab = withProjectPermission(
> >
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-1">
<p className="text-xl font-semibold text-mineshaft-100">Identities</p> <p className="text-xl font-semibold text-mineshaft-100">Identities</p>
<div className="flex w-full justify-end pr-4">
<a <a
href="https://infisical.com/docs/documentation/platform/identities/overview"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="https://infisical.com/docs/documentation/platform/identities/overview"
> >
<span className="flex w-max cursor-pointer items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white"> <div className="ml-1 mt-[0.16rem] inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
Documentation{" "} <FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
<span>Docs</span>
<FontAwesomeIcon <FontAwesomeIcon
icon={faArrowUpRightFromSquare} icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] ml-1 text-xs" className="mb-[0.07rem] ml-1.5 text-[10px]"
/> />
</span> </div>
</a> </a>
</div> </div>
<ProjectPermissionCan <ProjectPermissionCan
@@ -185,7 +191,7 @@ export const IdentityTab = withProjectPermission(
> >
{(isAllowed) => ( {(isAllowed) => (
<Button <Button
colorSchema="primary" colorSchema="secondary"
type="submit" type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("identity")} onClick={() => handlePopUpOpen("identity")}
@@ -231,7 +237,7 @@ export const IdentityTab = withProjectPermission(
</Th> </Th>
<Th className="w-1/3">Role</Th> <Th className="w-1/3">Role</Th>
<Th>Added on</Th> <Th>Added on</Th>
<Th className="w-16">{isFetching ? <Spinner size="xs" /> : null}</Th> <Th className="w-5">{isFetching ? <Spinner size="xs" /> : null}</Th>
</Tr> </Tr>
</THead> </THead>
<TBody> <TBody>
@@ -372,7 +378,20 @@ export const IdentityTab = withProjectPermission(
</div> </div>
</Td> </Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td> <Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
<Td className="flex justify-end space-x-2 opacity-0 duration-300 group-hover:opacity-100"> <Td className="flex justify-end space-x-2">
<Tooltip className="max-w-sm text-center" content="Options">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<IconButton
ariaLabel="Options"
colorSchema="secondary"
className="w-6"
variant="plain"
>
<FontAwesomeIcon icon={faEllipsisV} />
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent sideOffset={2} align="end">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.Identity, { a={subject(ProjectPermissionSub.Identity, {
@@ -380,7 +399,9 @@ export const IdentityTab = withProjectPermission(
})} })}
> >
{(isAllowed) => ( {(isAllowed) => (
<IconButton <DropdownMenuItem
icon={<FontAwesomeIcon icon={faCircleMinus} />}
isDisabled={!isAllowed}
onClick={(evt) => { onClick={(evt) => {
evt.stopPropagation(); evt.stopPropagation();
evt.preventDefault(); evt.preventDefault();
@@ -389,20 +410,14 @@ export const IdentityTab = withProjectPermission(
name name
}); });
}} }}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
> >
<FontAwesomeIcon icon={faXmark} /> Remove Identity From Project
</IconButton> </DropdownMenuItem>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
<IconButton ariaLabel="more-icon" variant="plain"> </DropdownMenuContent>
<FontAwesomeIcon icon={faEllipsisV} /> </DropdownMenu>
</IconButton> </Tooltip>
</Td> </Td>
</Tr> </Tr>
); );
@@ -56,12 +56,12 @@ export const MembersSection = () => {
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex justify-between"> <div className="mb-4 flex items-center justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Users</p> <p className="text-xl font-semibold text-mineshaft-100">Users</p>
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Member}> <ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Member}>
{(isAllowed) => ( {(isAllowed) => (
<Button <Button
colorSchema="primary" colorSchema="secondary"
type="submit" type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("addMember")} onClick={() => handlePopUpOpen("addMember")}
@@ -9,7 +9,7 @@ import {
faFilter, faFilter,
faMagnifyingGlass, faMagnifyingGlass,
faSearch, faSearch,
faTrash, faUserMinus,
faUsers faUsers
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -416,32 +416,40 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
</Td> </Td>
<Td> <Td>
{userId !== u?.id && ( {userId !== u?.id && (
<div className="flex items-center space-x-2 opacity-0 transition-opacity duration-300 group-hover:opacity-100"> <Tooltip className="max-w-sm text-center" content="Options">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<IconButton
ariaLabel="Options"
colorSchema="secondary"
className="w-6"
variant="plain"
>
<FontAwesomeIcon icon={faEllipsisV} />
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent sideOffset={2} align="end">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.Member} a={ProjectPermissionSub.Member}
> >
{(isAllowed) => ( {(isAllowed) => (
<IconButton <DropdownMenuItem
colorSchema="danger" icon={<FontAwesomeIcon icon={faUserMinus} />}
variant="plain" isDisabled={!isAllowed}
ariaLabel="update"
className="ml-4"
isDisabled={userId === u?.id || !isAllowed}
onClick={(evt) => { onClick={(evt) => {
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
handlePopUpOpen("removeMember", { username: u.username }); handlePopUpOpen("removeMember", { username: u.username });
}} }}
> >
<FontAwesomeIcon icon={faTrash} /> Remove User From Project
</IconButton> </DropdownMenuItem>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
<IconButton ariaLabel="more-icon" variant="plain"> </DropdownMenuContent>
<FontAwesomeIcon icon={faEllipsisV} /> </DropdownMenu>
</IconButton> </Tooltip>
</div>
)} )}
</Td> </Td>
</Tr> </Tr>
@@ -10,10 +10,10 @@ export const ProjectRoleListTab = withProjectPermission(
return ( return (
<motion.div <motion.div
key="role-list" key="role-list"
transition={{ duration: 0.1 }} transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: -30 }} initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }} animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }} exit={{ opacity: 0, translateX: 30 }}
> >
<ProjectRoleList /> <ProjectRoleList />
</motion.div> </motion.div>