Skip to content

Commit dc5aef0

Browse files
sean-brydonemrysal
andauthored
feat: preview default roles permissions (calcom#22621)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
1 parent e1d7631 commit dc5aef0

6 files changed

Lines changed: 64 additions & 22 deletions

File tree

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/roles/_components/AdvancedPermissionGroup.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface AdvancedPermissionGroupProps {
1616
resource: Resource;
1717
selectedPermissions: string[];
1818
onChange: (permissions: string[]) => void;
19+
disabled?: boolean;
1920
}
2021

2122
const INTERNAL_DATAACCESS_KEY = "_resource";
@@ -24,6 +25,7 @@ export function AdvancedPermissionGroup({
2425
resource,
2526
selectedPermissions,
2627
onChange,
28+
disabled,
2729
}: AdvancedPermissionGroupProps) {
2830
const { t } = useLocale();
2931
const { toggleSinglePermission, toggleResourcePermissionLevel } = usePermissions();
@@ -47,7 +49,9 @@ export function AdvancedPermissionGroup({
4749

4850
const handleToggleAll = (e: React.MouseEvent) => {
4951
e.stopPropagation(); // Stop event from triggering parent click
50-
onChange(toggleResourcePermissionLevel(resource, isAllSelected ? "none" : "all", selectedPermissions));
52+
if (!disabled) {
53+
onChange(toggleResourcePermissionLevel(resource, isAllSelected ? "none" : "all", selectedPermissions));
54+
}
5155
};
5256

5357
// Helper function to check if read permission is auto-enabled
@@ -77,6 +81,7 @@ export function AdvancedPermissionGroup({
7781
checked={isAllSelected}
7882
onCheckedChange={() => handleToggleAll}
7983
onClick={handleToggleAll}
84+
disabled={disabled}
8085
/>
8186
<span className="text-default text-sm font-medium leading-none">
8287
{t(resourceConfig._resource?.i18nKey || "")}
@@ -107,9 +112,12 @@ export function AdvancedPermissionGroup({
107112
checked={isChecked}
108113
className="mr-2"
109114
onCheckedChange={(checked) => {
110-
onChange(toggleSinglePermission(permission, !!checked, selectedPermissions));
115+
if (!disabled) {
116+
onChange(toggleSinglePermission(permission, !!checked, selectedPermissions));
117+
}
111118
}}
112119
onClick={(e) => e.stopPropagation()} // Stop checkbox clicks from affecting parent
120+
disabled={disabled}
113121
/>
114122
<div
115123
className="flex items-center gap-2"

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/roles/_components/RoleColorPicker.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
77
export interface RoleColorPickerProps {
88
value: string;
99
onChange: (color: string) => void;
10+
disabled?: boolean;
1011
}
1112

12-
export default function RoleColorPicker({ value, onChange }: RoleColorPickerProps) {
13+
export default function RoleColorPicker({ value, onChange, disabled }: RoleColorPickerProps) {
1314
const { t } = useLocale();
1415
const [, setCustomColor] = useState(value);
1516
const colorInputRef = useRef<HTMLInputElement>(null);
@@ -21,15 +22,18 @@ export default function RoleColorPicker({ value, onChange }: RoleColorPickerProp
2122
};
2223

2324
const handleColorSquareClick = () => {
24-
colorInputRef.current?.click();
25+
if (!disabled) {
26+
colorInputRef.current?.click();
27+
}
2528
};
2629

2730
return (
2831
<div className="mt-6">
2932
<button
3033
type="button"
3134
onClick={handleColorSquareClick}
32-
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">
35+
disabled={disabled}
36+
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">
3337
<div
3438
className="h-5 w-5 rounded border shadow-sm"
3539
style={{ backgroundColor: value }}

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/roles/_components/RoleSheet.tsx

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
6262
const { t } = useLocale();
6363
const router = useRouter();
6464
const isEditing = Boolean(role);
65+
const isSystemRole = role?.type === "SYSTEM";
6566
const [searchQuery, setSearchQuery] = useState("");
6667

6768
const defaultValues = useMemo(
@@ -171,7 +172,9 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
171172
<Sheet open={open} onOpenChange={onOpenChange}>
172173
<SheetContent>
173174
<SheetHeader>
174-
<SheetTitle>{isEditing ? t("edit_role") : t("create_role")}</SheetTitle>
175+
<SheetTitle>
176+
{isSystemRole ? t("view_role") : isEditing ? t("edit_role") : t("create_role")}
177+
</SheetTitle>
175178
</SheetHeader>
176179
<Form form={form} handleSubmit={onSubmit}>
177180
<div className="space-y-4 py-5">
@@ -181,11 +184,13 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
181184
label={t("role_name")}
182185
{...form.register("name")}
183186
placeholder={t("role_name_placeholder")}
187+
disabled={isSystemRole}
184188
/>
185189
</div>
186190
<RoleColorPicker
187191
value={color}
188192
onChange={(value) => form.setValue("color", value, { shouldDirty: true })}
193+
disabled={isSystemRole}
189194
/>
190195
</div>
191196

@@ -208,6 +213,7 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
208213
placeholder={t("search_permissions")}
209214
value={searchQuery}
210215
onChange={(e) => setSearchQuery(e.target.value)}
216+
disabled={isSystemRole}
211217
/>
212218
</div>
213219

@@ -217,6 +223,7 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
217223
resource={resource as Resource}
218224
selectedPermissions={permissions}
219225
onChange={(newPermissions) => form.setValue("permissions", newPermissions)}
226+
disabled={isSystemRole}
220227
/>
221228
))}
222229
</div>
@@ -239,6 +246,7 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
239246
resource={resource}
240247
permissions={permissions}
241248
onChange={(newPermissions) => form.setValue("permissions", newPermissions)}
249+
disabled={isSystemRole}
242250
/>
243251
))}
244252
</div>
@@ -247,18 +255,27 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
247255
</div>
248256
</div>
249257

250-
<SheetFooter>
251-
<Button
252-
type="button"
253-
color="secondary"
254-
onClick={() => onOpenChange(false)}
255-
disabled={createMutation.isPending || updateMutation.isPending}>
256-
{t("cancel")}
257-
</Button>
258-
<Button type="submit" loading={createMutation.isPending || updateMutation.isPending}>
259-
{isEditing ? t("save") : t("create")}
260-
</Button>
261-
</SheetFooter>
258+
{!isSystemRole && (
259+
<SheetFooter>
260+
<Button
261+
type="button"
262+
color="secondary"
263+
onClick={() => onOpenChange(false)}
264+
disabled={createMutation.isPending || updateMutation.isPending}>
265+
{t("cancel")}
266+
</Button>
267+
<Button type="submit" loading={createMutation.isPending || updateMutation.isPending}>
268+
{isEditing ? t("save") : t("create")}
269+
</Button>
270+
</SheetFooter>
271+
)}
272+
{isSystemRole && (
273+
<SheetFooter>
274+
<Button type="button" color="secondary" onClick={() => onOpenChange(false)}>
275+
{t("close")}
276+
</Button>
277+
</SheetFooter>
278+
)}
262279
</Form>
263280
</SheetContent>
264281
</Sheet>

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/roles/_components/RolesList.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,10 @@ export function RolesList({
8181
role={role}
8282
key={role.id}
8383
onClick={() => {
84-
// Cant edit system roles
84+
// For system roles, open in view-only mode
8585
if (role.type === "SYSTEM") {
86+
setSelectedRoleId(role.id);
87+
setIsOpen(true);
8688
return;
8789
}
8890

@@ -136,7 +138,9 @@ function RoleItem({
136138
<div
137139
className={classNames(
138140
"border-subtle flex p-3",
139-
canUpdate && role.type !== "SYSTEM" && "hover:bg-subtle cursor-pointer"
141+
(canUpdate && role.type !== "SYSTEM") || role.type === "SYSTEM"
142+
? "hover:bg-subtle cursor-pointer"
143+
: ""
140144
)}
141145
onClick={onClick}>
142146
<div className="flex w-full items-center gap-3 truncate">

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/roles/_components/SimplePermissionItem.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,15 @@ interface SimplePermissionItemProps {
1212
resource: string;
1313
permissions: string[];
1414
onChange: (permissions: string[]) => void;
15+
disabled?: boolean;
1516
}
1617

17-
export function SimplePermissionItem({ resource, permissions, onChange }: SimplePermissionItemProps) {
18+
export function SimplePermissionItem({
19+
resource,
20+
permissions,
21+
onChange,
22+
disabled,
23+
}: SimplePermissionItemProps) {
1824
const { t } = useLocale();
1925
const { getResourcePermissionLevel, toggleResourcePermissionLevel } = usePermissions();
2026

@@ -37,10 +43,12 @@ export function SimplePermissionItem({ resource, permissions, onChange }: Simple
3743
</span>
3844
<ToggleGroup
3945
onValueChange={(val) => {
40-
if (val) onChange(toggleResourcePermissionLevel(resource, val as PermissionLevel, permissions));
46+
if (val && !disabled)
47+
onChange(toggleResourcePermissionLevel(resource, val as PermissionLevel, permissions));
4148
}}
4249
value={getResourcePermissionLevel(resource, permissions)}
4350
options={options}
51+
disabled={disabled}
4452
/>
4553
</div>
4654
);

apps/web/public/static/locales/en/common.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,7 @@
683683
"send_invite_email": "Send an invite email",
684684
"role": "Role",
685685
"edit_role": "Edit Role",
686+
"view_role": "View Role",
686687
"edit_team": "Edit team",
687688
"editing_user": "Editing user",
688689
"editing_org": "Editing organization",

0 commit comments

Comments
 (0)