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;
+ }
}
};