mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-20 10:33:41 -05:00
Add moderation actions to user panel
This commit is contained in:
parent
05460e8130
commit
00c97fb5df
6 changed files with 177 additions and 4 deletions
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5E6267"><path d="M200-200v-80h560v80H200Zm14-160 266-400 266 400H214Zm266-80Zm-118 0h236L480-616 362-440Z"/></svg>
|
Before Width: | Height: | Size: 213 B |
1
web/src/icons/gavel.svg
Normal file
1
web/src/icons/gavel.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5E6267"><path d="M160-120v-80h480v80H160Zm226-194L160-540l84-86 228 226-86 86Zm254-254L414-796l86-84 226 226-86 86Zm184 408L302-682l56-56 522 522-56 56Z"/></svg>
|
After Width: | Height: | Size: 260 B |
|
@ -192,6 +192,34 @@ div.right-panel-content.user {
|
|||
}
|
||||
}
|
||||
|
||||
div.user-moderation {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5rem;
|
||||
|
||||
div.moderation-actions {
|
||||
.dangerous {
|
||||
color: var(--error-color);
|
||||
|
||||
svg {
|
||||
fill: var(--error-color)
|
||||
}
|
||||
}
|
||||
.invite {
|
||||
color: var(--primary-color);
|
||||
svg {
|
||||
fill: var(--primary-color)
|
||||
}
|
||||
}
|
||||
}
|
||||
button {
|
||||
padding: .25rem 0;
|
||||
width: 100%;
|
||||
gap: .25rem;
|
||||
justify-content: left;
|
||||
}
|
||||
}
|
||||
|
||||
div.errors {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -26,6 +26,7 @@ import UserExtendedProfile from "./UserExtendedProfile.tsx"
|
|||
import DeviceList from "./UserInfoDeviceList.tsx"
|
||||
import UserInfoError from "./UserInfoError.tsx"
|
||||
import MutualRooms from "./UserInfoMutualRooms.tsx"
|
||||
import UserModeration from "./UserModeration.tsx"
|
||||
|
||||
interface UserInfoProps {
|
||||
userID: UserID
|
||||
|
@ -77,6 +78,8 @@ const UserInfo = ({ userID }: UserInfoProps) => {
|
|||
</>}
|
||||
<DeviceList client={client} room={roomCtx?.store} userID={userID}/>
|
||||
<hr/>
|
||||
<UserModeration client={client} room={roomCtx?.store} member={memberEvt} userID={userID}/>
|
||||
<hr/>
|
||||
{errors?.length ? <>
|
||||
<UserInfoError errors={errors}/>
|
||||
<hr/>
|
||||
|
|
142
web/src/ui/rightpanel/UserModeration.tsx
Normal file
142
web/src/ui/rightpanel/UserModeration.tsx
Normal file
|
@ -0,0 +1,142 @@
|
|||
// gomuks - A Matrix client written in Go.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import { use, useState } from "react"
|
||||
import Client from "@/api/client.ts"
|
||||
import { RoomStateStore } from "@/api/statestore"
|
||||
import { MemDBEvent, MemberEventContent, Membership } from "@/api/types"
|
||||
import { ModalContext } from "@/ui/modal"
|
||||
import { RoomContext } from "@/ui/roomview/roomcontext.ts"
|
||||
import ConfirmWithMessageModal from "@/ui/timeline/menu/ConfirmWithMessageModal.tsx"
|
||||
import Gavel from "@/icons/gavel.svg?react"
|
||||
import PersonAdd from "@/icons/person-add.svg?react"
|
||||
import PersonRemove from "@/icons/person-remove.svg?react"
|
||||
|
||||
interface UserModerationProps {
|
||||
userID: string;
|
||||
client: Client;
|
||||
room?: RoomStateStore;
|
||||
member: MemDBEvent | null;
|
||||
}
|
||||
|
||||
const UserModeration = ({ userID, client, member }: UserModerationProps) => {
|
||||
const [actionInProgress, setActionInProgress] = useState(false)
|
||||
const roomCtx = use(RoomContext)
|
||||
const openModal = use(ModalContext)
|
||||
|
||||
const runAction = (mode: Membership) => {
|
||||
const callback = (reason: string) => {
|
||||
if (!roomCtx?.store.roomID) {
|
||||
console.error("Cannot action user without a room")
|
||||
return
|
||||
}
|
||||
const payload: MemberEventContent = {
|
||||
membership: mode,
|
||||
}
|
||||
if (reason) {
|
||||
payload["reason"] = reason
|
||||
}
|
||||
setActionInProgress(true)
|
||||
client.rpc
|
||||
.setState(roomCtx?.store.roomID, "m.room.member", userID, payload)
|
||||
.then(() => {
|
||||
console.debug("Actioned", userID)
|
||||
setActionInProgress(false)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error("Failed to action", e)
|
||||
setActionInProgress(false)
|
||||
})
|
||||
}
|
||||
return () => {
|
||||
openModal({
|
||||
dimmed: true,
|
||||
boxed: true,
|
||||
innerBoxClass: "confirm-message-modal",
|
||||
content: (
|
||||
<RoomContext value={roomCtx}>
|
||||
<ConfirmWithMessageModal
|
||||
title={`${mode.charAt(0).toUpperCase() + mode.slice(1)} User`}
|
||||
description={`Are you sure you want to ${mode} this user?`}
|
||||
placeholder="Optional reason"
|
||||
confirmButton={mode.charAt(0).toUpperCase() + mode.slice(1)}
|
||||
onConfirm={callback}
|
||||
/>
|
||||
</RoomContext>
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const membership = member?.content.membership || "leave"
|
||||
return (
|
||||
<div className="user-moderation">
|
||||
<h4>Moderation</h4>
|
||||
<div className="moderation-actions">
|
||||
{(["knock", "leave"].includes(membership) || !member) && (
|
||||
<button
|
||||
className="moderation-action invite"
|
||||
onClick={runAction("invite")}
|
||||
disabled={actionInProgress}
|
||||
>
|
||||
<PersonAdd />
|
||||
<span>Invite</span>
|
||||
</button>
|
||||
)}
|
||||
{["knock", "invite"].includes(membership) && (
|
||||
<button
|
||||
className="moderation-action dangerous"
|
||||
onClick={runAction("leave")}
|
||||
disabled={actionInProgress}
|
||||
>
|
||||
<PersonRemove />
|
||||
<span>{membership === "invite" ? "Revoke invitation" : "Reject join request"}</span>
|
||||
</button>
|
||||
)}
|
||||
{membership === "join" && (
|
||||
<button
|
||||
className="moderation-action dangerous"
|
||||
onClick={runAction("leave")}
|
||||
disabled={actionInProgress}
|
||||
>
|
||||
<PersonRemove />
|
||||
<span>Kick</span>
|
||||
</button>
|
||||
)}
|
||||
{membership !== "ban" && (
|
||||
<button
|
||||
className="moderation-action dangerous"
|
||||
onClick={runAction("ban")}
|
||||
disabled={actionInProgress}
|
||||
>
|
||||
<Gavel />
|
||||
<span>Ban</span>
|
||||
</button>
|
||||
)}
|
||||
{membership === "ban" && (
|
||||
<button
|
||||
className="moderation-action invite"
|
||||
onClick={runAction("leave")}
|
||||
disabled={actionInProgress}
|
||||
>
|
||||
<Gavel />
|
||||
<span>Unban</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default UserModeration
|
|
@ -20,7 +20,7 @@ import { ModalCloseContext } from "../../modal"
|
|||
import TimelineEvent from "../TimelineEvent.tsx"
|
||||
|
||||
interface ConfirmWithMessageProps {
|
||||
evt: MemDBEvent
|
||||
evt?: MemDBEvent
|
||||
title: string
|
||||
description: string
|
||||
placeholder: string
|
||||
|
@ -40,9 +40,9 @@ const ConfirmWithMessageModal = ({
|
|||
}
|
||||
return <form onSubmit={onConfirmWrapped}>
|
||||
<h3>{title}</h3>
|
||||
<div className="timeline-event-container">
|
||||
{evt && <div className="timeline-event-container">
|
||||
<TimelineEvent evt={evt} prevEvt={null} disableMenu={true} />
|
||||
</div>
|
||||
</div>}
|
||||
<div className="confirm-description">
|
||||
{description}
|
||||
</div>
|
||||
|
|
Loading…
Add table
Reference in a new issue