ui improvements

This commit is contained in:
miloschwartz
2026-03-26 16:37:31 -07:00
parent 0fecbe704b
commit e13a076939
33 changed files with 533 additions and 205 deletions

View File

@@ -95,7 +95,8 @@ function getActionsCategories(root: boolean) {
[t("actionListRole")]: "listRoles",
[t("actionUpdateRole")]: "updateRole",
[t("actionListAllowedRoleResources")]: "listRoleResources",
[t("actionAddUserRole")]: "addUserRole"
[t("actionAddUserRole")]: "addUserRole",
[t("actionSetUserOrgRoles")]: "setUserOrgRoles"
},
"Access Token": {
[t("actionGenerateAccessToken")]: "generateAccessToken",

View File

@@ -12,6 +12,13 @@ import { Button } from "@app/components/ui/button";
import { ArrowRight, ArrowUpDown, Crown, MoreHorizontal } from "lucide-react";
import { UsersDataTable } from "@app/components/UsersDataTable";
import { useState, useEffect } from "react";
import {
Popover,
PopoverContent,
PopoverTrigger
} from "@app/components/ui/popover";
import { Badge, badgeVariants } from "@app/components/ui/badge";
import { cn } from "@app/lib/cn";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { useOrgContext } from "@app/hooks/useOrgContext";
import { toast } from "@app/hooks/useToast";
@@ -36,10 +43,65 @@ export type UserRow = {
type: string;
idpVariant: string | null;
status: string;
role: string;
roleLabels: string[];
isOwner: boolean;
};
const MAX_ROLE_BADGES = 3;
function UserRoleBadges({ roleLabels }: { roleLabels: string[] }) {
const visible = roleLabels.slice(0, MAX_ROLE_BADGES);
const overflow = roleLabels.slice(MAX_ROLE_BADGES);
return (
<div className="flex flex-wrap items-center gap-1">
{visible.map((label, i) => (
<Badge key={`${label}-${i}`} variant="secondary">
{label}
</Badge>
))}
{overflow.length > 0 && (
<OverflowRolesPopover labels={overflow} />
)}
</div>
);
}
function OverflowRolesPopover({ labels }: { labels: string[] }) {
const [open, setOpen] = useState(false);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<button
type="button"
className={cn(
badgeVariants({ variant: "secondary" }),
"border-dashed"
)}
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
>
+{labels.length}
</button>
</PopoverTrigger>
<PopoverContent
align="start"
side="top"
className="w-auto max-w-xs p-2"
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
>
<ul className="space-y-1 text-sm">
{labels.map((label, i) => (
<li key={`${label}-${i}`}>{label}</li>
))}
</ul>
</PopoverContent>
</Popover>
);
}
type UsersTableProps = {
users: UserRow[];
};
@@ -124,7 +186,8 @@ export default function UsersTable({ users: u }: UsersTableProps) {
}
},
{
accessorKey: "role",
id: "role",
accessorFn: (row) => row.roleLabels.join(", "),
friendlyName: t("role"),
header: ({ column }) => {
return (
@@ -140,13 +203,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
);
},
cell: ({ row }) => {
const userRow = row.original;
return (
<div className="flex flex-row items-center gap-2">
<span>{userRow.role}</span>
</div>
);
return <UserRoleBadges roleLabels={row.original.roleLabels} />;
}
},
{

View File

@@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@app/lib/cn";
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-0",
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0",
{
variants: {
variant: {