blob: 59d91e4511b3859aa4863f43de2ee2878d77d031 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
"use client";
import * as React from "react";
import type { PermissionKey } from "./permissions";
import { INITIAL_ROLES } from "./roles-catalog";
import { INITIAL_USERS } from "./seed-users";
import type { RoleRow, UserRow } from "./types";
export type RoleInput = {
name: string;
description: string;
permissions: PermissionKey[];
};
type UsersAdminContextValue = {
users: UserRow[];
roles: RoleRow[];
toggleUserRole: (userId: string, roleId: string) => void;
addRole: (input: RoleInput, memberUserIds: string[]) => void;
updateRole: (roleId: string, input: RoleInput, memberUserIds: string[]) => void;
};
const UsersAdminContext = React.createContext<UsersAdminContextValue | null>(null);
let nextRoleSuffix = 1;
export function UsersAdminProvider({ children }: { children: React.ReactNode }) {
const [users, setUsers] = React.useState<UserRow[]>(INITIAL_USERS);
const [roles, setRoles] = React.useState<RoleRow[]>(INITIAL_ROLES);
function toggleUserRole(userId: string, roleId: string) {
const role = roles.find((r) => r.id === roleId);
if (!role) return;
setUsers((prev) =>
prev.map((user) => {
if (user.id !== userId) return user;
const hasRole = user.roles.some((r) => r.id === roleId);
const nextRoles = hasRole
? user.roles.filter((r) => r.id !== roleId)
: [...user.roles, role];
return { ...user, roles: nextRoles };
}),
);
}
function addRole(input: RoleInput, memberUserIds: string[]) {
const role: RoleRow = {
id: `role-custom-${nextRoleSuffix++}`,
name: input.name,
description: input.description,
is_system: false,
permissions: input.permissions,
};
setRoles((prev) => [...prev, role]);
if (memberUserIds.length > 0) {
const memberSet = new Set(memberUserIds);
setUsers((prev) =>
prev.map((user) =>
user.id && memberSet.has(user.id) ? { ...user, roles: [...user.roles, role] } : user,
),
);
}
}
// Role objects are embedded (by value) into each user's `roles` array
// rather than referenced by id, mirroring how the table/drawer render
// them — so updating a role here must also refresh the copy held by every
// member, add it to newly-assigned members, and drop it from anyone
// deselected.
function updateRole(roleId: string, input: RoleInput, memberUserIds: string[]) {
const existing = roles.find((r) => r.id === roleId);
const updated: RoleRow = {
id: roleId,
name: input.name,
description: input.description,
is_system: existing?.is_system ?? false,
permissions: input.permissions,
};
setRoles((prev) => prev.map((r) => (r.id === roleId ? updated : r)));
const memberSet = new Set(memberUserIds);
setUsers((prev) =>
prev.map((user) => {
const hasRole = user.roles.some((r) => r.id === roleId);
const shouldHaveRole = Boolean(user.id) && memberSet.has(user.id as string);
if (shouldHaveRole) {
const nextRoles = hasRole
? user.roles.map((r) => (r.id === roleId ? updated : r))
: [...user.roles, updated];
return { ...user, roles: nextRoles };
}
if (hasRole) {
return { ...user, roles: user.roles.filter((r) => r.id !== roleId) };
}
return user;
}),
);
}
return (
<UsersAdminContext.Provider value={{ users, roles, toggleUserRole, addRole, updateRole }}>
{children}
</UsersAdminContext.Provider>
);
}
export function useUsersAdmin(): UsersAdminContextValue {
const ctx = React.useContext(UsersAdminContext);
if (!ctx) throw new Error("useUsersAdmin must be used within UsersAdminProvider");
return ctx;
}