mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-13 05:12:50 +00:00
ban users
This commit is contained in:
1 parent
791fd98c8c
commit
8e554f6ed1
6 files changed
+221
-2
No files matched your search
@@ -246,6 +246,10 @@ class ApiConfig {
|
||||
return `${this.getScheme()}://${this.getNewApiHost()}/moderation/ip_bans/${ipAddress}/delete`;
|
||||
}
|
||||
|
||||
banUser() : string {
|
||||
return `${this.getScheme()}://${this.getNewApiHost()}/moderation/user_bans/manage_ban`;
|
||||
}
|
||||
|
||||
private getScheme() : string {
|
||||
return this.isLocalDev ? "http" : "https";
|
||||
}
|
||||
|
||||
@@ -71,6 +71,14 @@ class FrontendUrlConfig {
|
||||
static userProfilePage(userDisplayName: string) : string {
|
||||
return `/profile/${userDisplayName}`;
|
||||
}
|
||||
|
||||
static userProfileEditPage(userDisplayName: string) : string {
|
||||
return `/profile/${userDisplayName}/edit`;
|
||||
}
|
||||
|
||||
static userProfileBanPage(userDisplayName: string) : string {
|
||||
return `/profile/${userDisplayName}/ban`;
|
||||
}
|
||||
}
|
||||
|
||||
export { FrontendUrlConfig }
|
||||
@@ -14,7 +14,6 @@ export interface User {
|
||||
profile_markdown: string,
|
||||
profile_rendered_html: string,
|
||||
user_role_slug: string,
|
||||
banned: boolean,
|
||||
dark_mode: string,
|
||||
avatar_public_bucket_hash: string,
|
||||
disable_gravatar: boolean,
|
||||
@@ -28,6 +27,7 @@ export interface User {
|
||||
cashapp_username: string | undefined | null,
|
||||
created_at: string,
|
||||
badges: ProfileBadge[],
|
||||
maybe_moderator_fields: UserProfileModeratorFields | null | undefined,
|
||||
}
|
||||
|
||||
export interface ProfileBadge {
|
||||
@@ -38,6 +38,13 @@ export interface ProfileBadge {
|
||||
granted_at: string,
|
||||
}
|
||||
|
||||
export interface UserProfileModeratorFields {
|
||||
is_banned: boolean,
|
||||
maybe_mod_comments: string | null | undefined,
|
||||
maybe_mod_user_token: string | null | undefined,
|
||||
}
|
||||
|
||||
|
||||
export async function GetUserByUsername(username: string) : Promise<User | undefined> {
|
||||
const usernameLower = username.toLowerCase(); // NB: Until I standardize on display name vs username lookup.
|
||||
const endpoint = new ApiConfig().getProfile(usernameLower);
|
||||
|
||||
@@ -36,6 +36,7 @@ import { W2lResultDeleteFc } from './w2l/w2l_result_delete/W2lResultDeleteFc';
|
||||
import { W2lTemplateApproveFc } from './w2l/w2l_template_approve/W2lTemplateApproveFc';
|
||||
import { TtsModelListFc } from './tts/tts_model_list/TtsModelListFc';
|
||||
import { TtsModelListItem } from '../api/tts/ListTtsModels';
|
||||
import { ProfileBanFc } from './profile/profile_ban/ProfileBanFc';
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper,
|
||||
@@ -109,6 +110,12 @@ class NewVocodesContainer extends React.Component<Props, State> {
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/profile/:username/ban">
|
||||
<ProfileBanFc
|
||||
sessionWrapper={this.props.sessionWrapper}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/profile/:username">
|
||||
<ProfileFc
|
||||
sessionWrapper={this.props.sessionWrapper}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { ApiConfig } from '../../../../common/ApiConfig';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import { SessionWrapper } from '../../../../session/SessionWrapper';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { GetUserByUsername, User } from '../../../api/user/GetUserByUsername';
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper,
|
||||
}
|
||||
|
||||
function ProfileBanFc(props: Props) {
|
||||
const { username } : { username: string }= useParams();
|
||||
const userProfilePage = `/profile/${username}`;
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
// From endpoint
|
||||
const [userData, setUserData] = useState<User|undefined>(undefined);
|
||||
|
||||
// Form values
|
||||
const [modComments, setModComments] = useState<string>("");
|
||||
const [isBanned, setIsBanned] = useState<boolean>(false);
|
||||
|
||||
const getUserProfile = useCallback(async (username) => {
|
||||
const user = await GetUserByUsername(username);
|
||||
if (user) {
|
||||
setUserData(user);
|
||||
setIsBanned(user?.maybe_moderator_fields?.is_banned || false);
|
||||
setModComments(user?.maybe_moderator_fields?.maybe_mod_comments || "");
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
getUserProfile(username);
|
||||
}, [username, getUserProfile]);
|
||||
|
||||
const handleFormSubmit = (ev: React.FormEvent<HTMLFormElement>) : boolean => {
|
||||
ev.preventDefault();
|
||||
|
||||
const api = new ApiConfig();
|
||||
const endpointUrl = api.banUser();
|
||||
|
||||
const request = {
|
||||
username: userData?.username,
|
||||
is_banned: isBanned,
|
||||
mod_notes: modComments,
|
||||
}
|
||||
|
||||
fetch(endpointUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(request),
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res.success) {
|
||||
history.push(userProfilePage);
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!userData) {
|
||||
// Waiting for load.
|
||||
return <span />;
|
||||
}
|
||||
|
||||
if (!!userData && !props.sessionWrapper.canEditUserProfile(username)) {
|
||||
// Loading and we don't have access.
|
||||
history.push(userProfilePage);
|
||||
}
|
||||
|
||||
|
||||
const handleModCommentsChange = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
ev.preventDefault();
|
||||
const textValue = (ev.target as HTMLInputElement).value;
|
||||
setModComments(textValue);
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleIsBannedChange = (ev: React.FormEvent<HTMLSelectElement>) => {
|
||||
let bannedState = false;
|
||||
switch ((ev.target as HTMLSelectElement).value) {
|
||||
case "true":
|
||||
bannedState = true;
|
||||
break;
|
||||
case "TRUE":
|
||||
bannedState = true;
|
||||
break;
|
||||
}
|
||||
setIsBanned(bannedState)
|
||||
};
|
||||
|
||||
let viewLinkUrl = `/profile/${userData?.username}`;
|
||||
|
||||
let isDisabled = userData === undefined;
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="subtitle is-2">Profile & Preferences</h2>
|
||||
|
||||
<Link to={viewLinkUrl}>< Back to profile</Link>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<form onSubmit={handleFormSubmit}>
|
||||
<fieldset disabled={isDisabled}>
|
||||
|
||||
<div className="field">
|
||||
<label className="label">Is Banned?</label>
|
||||
<div className="control select">
|
||||
<select
|
||||
name="default_pretrained_vocoder"
|
||||
onChange={handleIsBannedChange}
|
||||
value={isBanned ? "true" : "false"}
|
||||
>
|
||||
<option value="true">Banned</option>
|
||||
<option value="false">Not Banned</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label className="label">Moderator Comments (Short)</label>
|
||||
<div className="control has-icons-left has-icons-right">
|
||||
<input
|
||||
onChange={handleModCommentsChange}
|
||||
className="input"
|
||||
type="text"
|
||||
placeholder="Moderator Comments"
|
||||
value={modComments}
|
||||
/>
|
||||
</div>
|
||||
{/*<p className="help">{invalidReason}</p>*/}
|
||||
</div>
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
<button className="button is-danger is-large is-fullwidth">Update Ban</button>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<br />
|
||||
|
||||
<div>
|
||||
<p>Notes on banned users:</p>
|
||||
<ul>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { ProfileBanFc };
|
||||
@@ -12,6 +12,7 @@ import { faDiscord, faFirefox, faGithub, faTwitch, faTwitter } from '@fortawesom
|
||||
import { faClock, faDollarSign } from '@fortawesome/free-solid-svg-icons';
|
||||
import { GetUserByUsername, User } from '../../../api/user/GetUserByUsername';
|
||||
import { format } from 'date-fns';
|
||||
import { FrontendUrlConfig } from '../../../../common/FrontendUrlConfig';
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper,
|
||||
@@ -43,7 +44,7 @@ function ProfileFc(props: Props) {
|
||||
let editProfileButton = <span />
|
||||
|
||||
if (props.sessionWrapper.canEditUserProfile(userData.username)) {
|
||||
const editLinkUrl = `/profile/${userData.username}/edit`;
|
||||
const editLinkUrl = FrontendUrlConfig.userProfileEditPage(userData.username);
|
||||
|
||||
// Mods shouldn't edit preferences.
|
||||
const buttonLabel = props.sessionWrapper.userTokenMatches(userData.user_token) ?
|
||||
@@ -59,6 +60,26 @@ function ProfileFc(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
let banUserButton = <span />
|
||||
|
||||
if (props.sessionWrapper.canBanUsers()) {
|
||||
const currentlyBanned = userData.maybe_moderator_fields?.is_banned;
|
||||
const banLinkUrl = FrontendUrlConfig.userProfileBanPage(userData.username);
|
||||
const buttonLabel = currentlyBanned ? "Unban User" : "Ban User";
|
||||
const banButtonCss = currentlyBanned ?
|
||||
"button is-warning is-large is-fullwidth" :
|
||||
"button is-danger is-large is-fullwidth";
|
||||
|
||||
banUserButton = (
|
||||
<>
|
||||
<Link
|
||||
className={banButtonCss}
|
||||
to={banLinkUrl}>{buttonLabel}</Link>
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let profileRows : Array<JSX.Element> = [];
|
||||
|
||||
if (userData.website_url !== undefined && userData.website_url !== null) {
|
||||
@@ -209,6 +230,7 @@ function ProfileFc(props: Props) {
|
||||
</h1>
|
||||
|
||||
{editProfileButton}
|
||||
{banUserButton}
|
||||
|
||||
<div
|
||||
className="profile content is-medium"
|
||||
|
||||
Reference in new issue
Block a user