Add dedicated bot, workspace and user settings pages

This commit is contained in:
2026-10-19 13:17:17 +01:00
parent 731ea73486
commit 7667f2bc2f
74 changed files with 2183 additions and 358 deletions
@@ -0,0 +1,25 @@
import AddModalShell from "@/src/layouts/bots/(partials)/add-modal-shell";
import type { BUN_SQLITE_COBBLE_USERS_JOIN } from "@/src/types/sql-joins";
import UserForm from "@/src/pages/admin/people/create/(partials)/user-form";
type Props = {
open: boolean;
onClose: () => void;
onSaved: (params: { person: BUN_SQLITE_COBBLE_USERS_JOIN; msg: string }) => void;
};
/**
* Adds a user in a modal. Editing one happens on its settings page.
*/
export default function NewUserModal({ open, onClose, onSaved }: Props) {
return (
<AddModalShell
open={open}
setOpen={(next) => !next && onClose()}
title="Add a user"
description="They get their own coordinator and main computer, and sign in with this password."
>
{open ? <UserForm onCancel={onClose} onSaved={onSaved} /> : null}
</AddModalShell>
);
}
@@ -1,33 +0,0 @@
import AddModalShell from "@/src/layouts/bots/(partials)/add-modal-shell";
import type { BUN_SQLITE_COBBLE_USERS_JOIN } from "@/src/types/sql-joins";
import UserForm from "@/src/pages/admin/people/create/(partials)/user-form";
type Props = {
/**
* Edits this user; adds one when not given.
*/
person?: BUN_SQLITE_COBBLE_USERS_JOIN;
open: boolean;
onClose: () => void;
onSaved: (params: { person: BUN_SQLITE_COBBLE_USERS_JOIN; msg: string }) => void;
};
/**
* Adds or edits a user in a modal.
*/
export default function UserEditModal({ person, open, onClose, onSaved }: Props) {
return (
<AddModalShell
open={open}
setOpen={(next) => !next && onClose()}
title={person ? `Edit ${person.first_name || "the user"}` : "Add a user"}
description={
person
? "Leave the password empty to keep theirs."
: "They get their own coordinator and main computer, and sign in with this password."
}
>
{open ? <UserForm key={person?.id || "new"} person={person} onCancel={onClose} onSaved={onSaved} /> : null}
</AddModalShell>
);
}
@@ -4,7 +4,6 @@ import type { BUN_SQLITE_COBBLE_USERS_JOIN } from "@/src/types/sql-joins";
import type { BUN_SQLITE_COBBLE_USER_TYPES } from "@/db/types/db";
import parseUserTypes from "@/src/utils/parse-user-types";
import canRevokeUser from "@/src/utils/rbac/can-revoke-user";
import canUpdateUser from "@/src/utils/rbac/can-update-user";
import SettingsListRow from "../../(partials)/settings-list-row";
import SettingsRowAction from "../../(partials)/settings-row-action";
@@ -13,22 +12,20 @@ type Props = {
actor_user_id?: number | string;
actor_user_types?: BUN_SQLITE_COBBLE_USER_TYPES[] | null;
busy?: boolean;
onEdit: () => void;
onRevoke: () => void;
onDelete: () => void;
};
/**
* One user: their photo, name, user types and email, then Edit, Revoke
* (not once revoked) and Delete where the actor may (`canUpdateUser`,
* `canRevokeUser`; never revoking or deleting yourself).
* One user: their photo, name, user types and email. The title opens
* their settings page; Revoke (not once revoked) and Delete sit at the
* end where the actor may (`canRevokeUser`; never yourself).
*/
export default function UserSettingsRow({
person,
actor_user_id,
actor_user_types,
busy,
onEdit,
onRevoke,
onDelete,
}: Props) {
@@ -42,25 +39,17 @@ export default function UserSettingsRow({
.join(", ");
const is_self = person.id == actor_user_id;
const target_user_types = user_types.map((user_type) => ({ user_type }));
const can_edit = canUpdateUser({ actor_user_types, actor_user_id, target_user_id: person.id, target_user_types });
const can_delete = !is_self && canRevokeUser({ user_types: actor_user_types, target_user_types });
const can_revoke = can_delete && !user_types.includes("revoked");
return (
<SettingsListRow
avatar={<UserAvatar person={person} size="md" />}
href={`/admin/settings/users/${person.id}`}
title={name}
badge={is_self ? "You" : undefined}
subtitle={[types_title, person.email].filter(Boolean).join(" · ")}
>
{can_edit ? (
<SettingsRowAction
title={`Edit ${name}`}
label="Edit"
disabled={busy}
onClick={onEdit}
/>
) : null}
{can_revoke ? (
<SettingsRowAction
title={`Revoke ${name}`}