diff --git a/README.md b/README.md index c710a10..4be433f 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ The following changes are already implemented: * 🛑 [Prevent accidental user overwrites](https://github.com/etkecc/synapse-admin/pull/139) * 🔍 [Allow providing login form details via GET params](https://github.com/etkecc/synapse-admin/pull/140) * 🎨 [Add preferred theme colors to login page and footer](https://github.com/etkecc/synapse-admin/pull/155) +* 🔰 [Add "Assign Admin" button to the rooms](https://github.com/etkecc/synapse-admin/pull/156) _the list will be updated as new changes are added_ diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 0566aa6..21195d1 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -13,8 +13,6 @@ const Footer = () => { } }, []); - console.log(theme); - return ( ; export const RoomDirectoryUnpublishButton = (props: DeleteButtonProps) => { @@ -154,6 +153,7 @@ export const RoomDirectoryList = () => ( + ); diff --git a/src/resources/rooms.tsx b/src/resources/rooms.tsx index 526c8bf..58af12c 100644 --- a/src/resources/rooms.tsx +++ b/src/resources/rooms.tsx @@ -11,11 +11,11 @@ import Box from "@mui/material/Box"; import { useTheme } from "@mui/material/styles"; import { BooleanField, - BulkDeleteButton, DateField, + EditButton, + WrapperField, Datagrid, DatagridConfigurable, - DeleteButton, ExportButton, FunctionField, List, @@ -32,13 +32,15 @@ import { ShowProps, Tab, TabbedShowLayout, - TextField, + TextField as RaTextField, TopToolbar, useRecordContext, useTranslate, useListContext, + useNotify, } from "react-admin"; +import TextField from "@mui/material/TextField"; import { RoomDirectoryBulkUnpublishButton, RoomDirectoryBulkPublishButton, @@ -48,7 +50,14 @@ import { import { DATE_FORMAT } from "../components/date"; import DeleteRoomButton from "../components/DeleteRoomButton"; import AvatarField from "../components/AvatarField"; - +import { Room } from "../synapse/dataProvider"; +import { useMutation } from "@tanstack/react-query"; +import { useDataProvider } from "react-admin"; +import { Confirm } from "react-admin"; +import { useState } from "react"; +import Button from "@mui/material/Button"; +import PersonIcon from '@mui/icons-material/Person'; +import Typography from "@mui/material/Typography"; const RoomPagination = () => ; const RoomTitle = () => { @@ -76,6 +85,7 @@ const RoomShowActions = () => { return ( {publishButton} + { ); }; +export const MakeAdminBtn = () => { + const record = useRecordContext() as Room; + + if (!record) { + return null; + } + + if (record.joined_local_members < 1) { + return null; + } + + + const ownMXID = localStorage.getItem("user_id") || ""; + const [open, setOpen] = useState(false); + const [userIdValue, setUserIdValue] = useState(ownMXID); + const dataProvider = useDataProvider(); + const notify = useNotify(); + const translate = useTranslate(); + + const { mutate, isPending } = useMutation({ + mutationFn: async () => { + try { + const result = await dataProvider.makeRoomAdmin(record.room_id, userIdValue); + if (!result.success) { + throw new Error(result.error); + } + } catch (error) { + throw error; + } + }, + onSuccess: () => { + notify("resources.rooms.action.make_admin.success", { type: "success" }); + setOpen(false); + setUserIdValue(""); + }, + onError: (err) => { + const errorMessage = err instanceof Error ? err.message : "Unknown error"; + notify("resources.rooms.action.make_admin.failure", { type: "error", messageArgs: { errMsg: errorMessage } }); + setOpen(false); + setUserIdValue(""); + } + }); + + const handleChange = (event: React.ChangeEvent) => { + setUserIdValue(event.target.value); + }; + + const handleConfirm = async () => { + mutate(); + setOpen(false); + }; + + const handleDialogClose = () => { + setOpen(false); + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + handleConfirm(); + } + }; + + return (<> + + + {translate("resources.rooms.action.make_admin.content")} + + } + /> + ); +}; + export const RoomShow = (props: ShowProps) => { const translate = useTranslate(); + const record = useRecordContext(); return ( } title={}> @@ -96,28 +195,29 @@ export const RoomShow = (props: ShowProps) => { sx={{ height: "120px", width: "120px" }} label="resources.rooms.fields.avatar" /> - - - - + + + + - + } path="detail"> - - - - - - + + + + + + } path="members"> + "/users/" + id} bulkActionButtons={false}> - + { sortable={false} link="" > - + @@ -185,11 +285,11 @@ export const RoomShow = (props: ShowProps) => { } path="state"> - + `${JSON.stringify(record.content, null, 2)}`} /> - + @@ -206,10 +306,10 @@ export const RoomShow = (props: ShowProps) => { - + - + @@ -270,12 +370,13 @@ export const RoomList = (props: ListProps) => { }} /> record["name"] || record["canonical_alias"] || record["id"]} /> - - - - + + + + + ); diff --git a/src/resources/users.tsx b/src/resources/users.tsx index 9e8bd0d..60f6b3d 100644 --- a/src/resources/users.tsx +++ b/src/resources/users.tsx @@ -79,6 +79,7 @@ import { useFormContext } from "react-hook-form"; import { ExperimentalFeaturesList } from "../components/ExperimentalFeatures"; import { UserRateLimits } from "../components/UserRateLimits"; import { User, UsernameAvailabilityResult } from "../synapse/dataProvider"; +import { MakeAdminBtn } from "./rooms"; const choices_medium = [ { id: "email", name: "resources.users.email" }, @@ -526,6 +527,7 @@ export const UserEdit = (props: EditProps) => { > + diff --git a/src/synapse/dataProvider.ts b/src/synapse/dataProvider.ts index bb030da..2feee38 100644 --- a/src/synapse/dataProvider.ts +++ b/src/synapse/dataProvider.ts @@ -52,7 +52,7 @@ interface Action { body?: Record; } -interface Room { +export interface Room { room_id: string; name?: string; canonical_alias?: string; @@ -273,6 +273,7 @@ export interface SynapseDataProvider extends DataProvider { getRateLimits: (id: Identifier) => Promise; setRateLimits: (id: Identifier, rateLimits: RateLimitsModel) => Promise; checkUsernameAvailability: (username: string) => Promise; + makeRoomAdmin: (room_id: string, user_id: string) => Promise<{ success: boolean; error?: string; errcode?: string }>; } const resourceMap = { @@ -866,6 +867,20 @@ const baseDataProvider: SynapseDataProvider = { } throw error; } + }, + makeRoomAdmin: async (room_id: string, user_id: string) => { + const base_url = storage.getItem("base_url"); + + const endpoint_url = `${base_url}/_synapse/admin/v1/rooms/${encodeURIComponent(room_id)}/make_room_admin`; + try { + const { json } = await jsonClient(endpoint_url, { method: "POST", body: JSON.stringify({ user_id }) }); + return { success: true }; + } catch (error) { + if (error instanceof HttpError) { + return { success: false, error: error.body.error, errcode: error.body.errcode }; + } + throw error; + } } };