feat: preview default roles permissions (#22621)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
co-authored by
Alex van Andel
parent
e1d7631215
commit
dc5aef0dd4
+10
-2
@@ -16,6 +16,7 @@ interface AdvancedPermissionGroupProps {
|
||||
resource: Resource;
|
||||
selectedPermissions: string[];
|
||||
onChange: (permissions: string[]) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const INTERNAL_DATAACCESS_KEY = "_resource";
|
||||
@@ -24,6 +25,7 @@ export function AdvancedPermissionGroup({
|
||||
resource,
|
||||
selectedPermissions,
|
||||
onChange,
|
||||
disabled,
|
||||
}: AdvancedPermissionGroupProps) {
|
||||
const { t } = useLocale();
|
||||
const { toggleSinglePermission, toggleResourcePermissionLevel } = usePermissions();
|
||||
@@ -47,7 +49,9 @@ export function AdvancedPermissionGroup({
|
||||
|
||||
const handleToggleAll = (e: React.MouseEvent) => {
|
||||
e.stopPropagation(); // Stop event from triggering parent click
|
||||
onChange(toggleResourcePermissionLevel(resource, isAllSelected ? "none" : "all", selectedPermissions));
|
||||
if (!disabled) {
|
||||
onChange(toggleResourcePermissionLevel(resource, isAllSelected ? "none" : "all", selectedPermissions));
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to check if read permission is auto-enabled
|
||||
@@ -77,6 +81,7 @@ export function AdvancedPermissionGroup({
|
||||
checked={isAllSelected}
|
||||
onCheckedChange={() => handleToggleAll}
|
||||
onClick={handleToggleAll}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<span className="text-default text-sm font-medium leading-none">
|
||||
{t(resourceConfig._resource?.i18nKey || "")}
|
||||
@@ -107,9 +112,12 @@ export function AdvancedPermissionGroup({
|
||||
checked={isChecked}
|
||||
className="mr-2"
|
||||
onCheckedChange={(checked) => {
|
||||
onChange(toggleSinglePermission(permission, !!checked, selectedPermissions));
|
||||
if (!disabled) {
|
||||
onChange(toggleSinglePermission(permission, !!checked, selectedPermissions));
|
||||
}
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()} // Stop checkbox clicks from affecting parent
|
||||
disabled={disabled}
|
||||
/>
|
||||
<div
|
||||
className="flex items-center gap-2"
|
||||
|
||||
+7
-3
@@ -7,9 +7,10 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
export interface RoleColorPickerProps {
|
||||
value: string;
|
||||
onChange: (color: string) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export default function RoleColorPicker({ value, onChange }: RoleColorPickerProps) {
|
||||
export default function RoleColorPicker({ value, onChange, disabled }: RoleColorPickerProps) {
|
||||
const { t } = useLocale();
|
||||
const [, setCustomColor] = useState(value);
|
||||
const colorInputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -21,7 +22,9 @@ export default function RoleColorPicker({ value, onChange }: RoleColorPickerProp
|
||||
};
|
||||
|
||||
const handleColorSquareClick = () => {
|
||||
colorInputRef.current?.click();
|
||||
if (!disabled) {
|
||||
colorInputRef.current?.click();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -29,7 +32,8 @@ export default function RoleColorPicker({ value, onChange }: RoleColorPickerProp
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleColorSquareClick}
|
||||
className="bg-default text-default border-default hover:bg-muted hover:text-emphasis focus:bg-subtle focus-visible:shadow-outline-gray-focused shadow-outline-gray-rested enabled:hover:shadow-outline-gray-hover enabled:active:shadow-outline-gray-active flex h-8 w-8 items-center justify-center gap-2 rounded-[10px] border text-sm transition-shadow duration-200 focus-visible:outline-none focus-visible:ring-0">
|
||||
disabled={disabled}
|
||||
className="bg-default text-default border-default hover:bg-muted hover:text-emphasis focus:bg-subtle focus-visible:shadow-outline-gray-focused shadow-outline-gray-rested enabled:hover:shadow-outline-gray-hover enabled:active:shadow-outline-gray-active flex h-8 w-8 items-center justify-center gap-2 rounded-[10px] border text-sm transition-shadow duration-200 focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50">
|
||||
<div
|
||||
className="h-5 w-5 rounded border shadow-sm"
|
||||
style={{ backgroundColor: value }}
|
||||
|
||||
+30
-13
@@ -62,6 +62,7 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const isEditing = Boolean(role);
|
||||
const isSystemRole = role?.type === "SYSTEM";
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
const defaultValues = useMemo(
|
||||
@@ -171,7 +172,9 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent>
|
||||
<SheetHeader>
|
||||
<SheetTitle>{isEditing ? t("edit_role") : t("create_role")}</SheetTitle>
|
||||
<SheetTitle>
|
||||
{isSystemRole ? t("view_role") : isEditing ? t("edit_role") : t("create_role")}
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<Form form={form} handleSubmit={onSubmit}>
|
||||
<div className="space-y-4 py-5">
|
||||
@@ -181,11 +184,13 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
|
||||
label={t("role_name")}
|
||||
{...form.register("name")}
|
||||
placeholder={t("role_name_placeholder")}
|
||||
disabled={isSystemRole}
|
||||
/>
|
||||
</div>
|
||||
<RoleColorPicker
|
||||
value={color}
|
||||
onChange={(value) => form.setValue("color", value, { shouldDirty: true })}
|
||||
disabled={isSystemRole}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -208,6 +213,7 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
|
||||
placeholder={t("search_permissions")}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
disabled={isSystemRole}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -217,6 +223,7 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
|
||||
resource={resource as Resource}
|
||||
selectedPermissions={permissions}
|
||||
onChange={(newPermissions) => form.setValue("permissions", newPermissions)}
|
||||
disabled={isSystemRole}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -239,6 +246,7 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
|
||||
resource={resource}
|
||||
permissions={permissions}
|
||||
onChange={(newPermissions) => form.setValue("permissions", newPermissions)}
|
||||
disabled={isSystemRole}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -247,18 +255,27 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SheetFooter>
|
||||
<Button
|
||||
type="button"
|
||||
color="secondary"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={createMutation.isPending || updateMutation.isPending}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button type="submit" loading={createMutation.isPending || updateMutation.isPending}>
|
||||
{isEditing ? t("save") : t("create")}
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
{!isSystemRole && (
|
||||
<SheetFooter>
|
||||
<Button
|
||||
type="button"
|
||||
color="secondary"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={createMutation.isPending || updateMutation.isPending}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button type="submit" loading={createMutation.isPending || updateMutation.isPending}>
|
||||
{isEditing ? t("save") : t("create")}
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
)}
|
||||
{isSystemRole && (
|
||||
<SheetFooter>
|
||||
<Button type="button" color="secondary" onClick={() => onOpenChange(false)}>
|
||||
{t("close")}
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
)}
|
||||
</Form>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
+6
-2
@@ -81,8 +81,10 @@ export function RolesList({
|
||||
role={role}
|
||||
key={role.id}
|
||||
onClick={() => {
|
||||
// Cant edit system roles
|
||||
// For system roles, open in view-only mode
|
||||
if (role.type === "SYSTEM") {
|
||||
setSelectedRoleId(role.id);
|
||||
setIsOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,7 +138,9 @@ function RoleItem({
|
||||
<div
|
||||
className={classNames(
|
||||
"border-subtle flex p-3",
|
||||
canUpdate && role.type !== "SYSTEM" && "hover:bg-subtle cursor-pointer"
|
||||
(canUpdate && role.type !== "SYSTEM") || role.type === "SYSTEM"
|
||||
? "hover:bg-subtle cursor-pointer"
|
||||
: ""
|
||||
)}
|
||||
onClick={onClick}>
|
||||
<div className="flex w-full items-center gap-3 truncate">
|
||||
|
||||
+10
-2
@@ -12,9 +12,15 @@ interface SimplePermissionItemProps {
|
||||
resource: string;
|
||||
permissions: string[];
|
||||
onChange: (permissions: string[]) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function SimplePermissionItem({ resource, permissions, onChange }: SimplePermissionItemProps) {
|
||||
export function SimplePermissionItem({
|
||||
resource,
|
||||
permissions,
|
||||
onChange,
|
||||
disabled,
|
||||
}: SimplePermissionItemProps) {
|
||||
const { t } = useLocale();
|
||||
const { getResourcePermissionLevel, toggleResourcePermissionLevel } = usePermissions();
|
||||
|
||||
@@ -37,10 +43,12 @@ export function SimplePermissionItem({ resource, permissions, onChange }: Simple
|
||||
</span>
|
||||
<ToggleGroup
|
||||
onValueChange={(val) => {
|
||||
if (val) onChange(toggleResourcePermissionLevel(resource, val as PermissionLevel, permissions));
|
||||
if (val && !disabled)
|
||||
onChange(toggleResourcePermissionLevel(resource, val as PermissionLevel, permissions));
|
||||
}}
|
||||
value={getResourcePermissionLevel(resource, permissions)}
|
||||
options={options}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -683,6 +683,7 @@
|
||||
"send_invite_email": "Send an invite email",
|
||||
"role": "Role",
|
||||
"edit_role": "Edit Role",
|
||||
"view_role": "View Role",
|
||||
"edit_team": "Edit team",
|
||||
"editing_user": "Editing user",
|
||||
"editing_org": "Editing organization",
|
||||
|
||||
Reference in New Issue
Block a user