'use client' /** * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright (c) Infonomic Company Limited */ /** * User-roles drawer. * * Renders a flat checkbox list of every available role, pre-checked * from the user's current assignments. On save we wholesale-replace * the user's role-set via `setUserRoles`; the response carries the * authoritative stored set so the editor's "initial" baseline resets * cleanly. * * Standard drawer pattern (no view/edit mode toggle) — role lists are * short by design and the drawer is a short-lived edit context, not * a steady-state inspector. Save + Cancel are always visible; Save * is disabled until dirty. */ import { useState } from 'react' import { useTranslation } from '@byline/i18n/react' import { Alert, Button, Checkbox, LoaderEllipsis } from '@byline/ui/react' import cx from 'clsx' import { useBylineAdminServices } from '../../../services/admin-services-context.js' import styles from './roles.module.css' import type { AdminRoleResponse, UserRolesResponse } from '../../admin-roles/index.js' import type { AdminUserResponse } from '../index.js' interface UserRolesProps { user: AdminUserResponse allRoles: AdminRoleResponse[] initialRoleIds: string[] onClose?: () => void onSaved?: (response: UserRolesResponse) => void } function setsEqual(a: ReadonlySet, b: ReadonlySet): boolean { if (a.size !== b.size) return false for (const item of a) if (!b.has(item)) return false return true } export function UserRoles({ user, allRoles, initialRoleIds, onClose, onSaved }: UserRolesProps) { const { setUserRoles } = useBylineAdminServices() const { t } = useTranslation('byline-admin') const [initialSet, setInitialSet] = useState>(() => new Set(initialRoleIds)) const [selected, setSelected] = useState>(() => new Set(initialRoleIds)) const [saving, setSaving] = useState(false) const [error, setError] = useState(null) const [successMessage, setSuccessMessage] = useState(null) const isDirty = !setsEqual(selected, initialSet) function handleToggle(roleId: string, checked: boolean): void { setSelected((current) => { const next = new Set(current) if (checked) next.add(roleId) else next.delete(roleId) return next }) setSuccessMessage(null) } async function handleSave(): Promise { if (saving) return setSaving(true) setError(null) setSuccessMessage(null) try { const response = await setUserRoles({ data: { userId: user.id, roleIds: Array.from(selected) }, }) const storedSet = new Set(response.roles.map((r) => r.id)) setInitialSet(storedSet) setSelected(new Set(storedSet)) setSuccessMessage(t('common.feedback.saved')) onSaved?.(response) } catch (err) { const code = getErrorCode(err) if (code === 'admin.roles.userNotFound') { setError(t('adminUsers.roles.errors.userNotFound')) } else if (code === 'admin.roles.notFound') { setError(t('adminUsers.roles.errors.roleNotFound')) } else { setError(t('adminUsers.roles.errors.fallback')) } } finally { setSaving(false) } } return (
{error ? {error} : null} {successMessage ? {successMessage} : null} {allRoles.length === 0 ? (

{t('adminUsers.roles.emptyCatalog')}

) : (
{allRoles.map((role) => (
handleToggle(role.id, checked === true)} containerClasses={cx('byline-user-roles-checkbox-auto', styles['checkbox-auto'])} componentClasses={cx('byline-user-roles-checkbox-auto', styles['checkbox-auto'])} />
))}
)}
) } function getErrorCode(err: unknown): string | null { return typeof (err as { code?: unknown })?.code === 'string' ? (err as { code: string }).code : null }