diff options
Diffstat (limited to 'web/source/settings/admin/accounts/detail')
3 files changed, 386 insertions, 0 deletions
| diff --git a/web/source/settings/admin/accounts/detail/actions.tsx b/web/source/settings/admin/accounts/detail/actions.tsx new file mode 100644 index 000000000..75ab8db6e --- /dev/null +++ b/web/source/settings/admin/accounts/detail/actions.tsx @@ -0,0 +1,89 @@ +/* +	GoToSocial +	Copyright (C) GoToSocial Authors admin@gotosocial.org +	SPDX-License-Identifier: AGPL-3.0-or-later + +	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 <http://www.gnu.org/licenses/>. +*/ + +import React from "react"; + +import { useActionAccountMutation } from "../../../lib/query"; + +import MutationButton from "../../../components/form/mutation-button"; + +import useFormSubmit from "../../../lib/form/submit"; +import { +	useValue, +	useTextInput, +	useBoolInput, +} from "../../../lib/form"; + +import { Checkbox, TextInput } from "../../../components/form/inputs"; +import { AdminAccount } from "../../../lib/types/account"; + +export interface AccountActionsProps { +	account: AdminAccount, +} + +export function AccountActions({ account }: AccountActionsProps) { +	const form = { +		id: useValue("id", account.id), +		reason: useTextInput("text") +	}; + +	const reallySuspend = useBoolInput("reallySuspend"); +	const [accountAction, result] = useFormSubmit(form, useActionAccountMutation()); + +	return ( +		<form +			onSubmit={accountAction} +			aria-labelledby="account-moderation-actions" +		> +			<h3 id="account-moderation-actions">Account Moderation Actions</h3> +			<div> +				Currently only the "suspend" action is implemented.<br/> +				Suspending an account will delete it from your server, and remove all of its media, posts, relationships, etc.<br/> +				If the suspended account is local, suspending will also send out a "delete" message to other servers, requesting them to remove its data from their instance as well.<br/> +				<b>Account suspension cannot be reversed.</b> +			</div> +			<TextInput +				field={form.reason} +				placeholder="Reason for this action" +			/> +			<div className="action-buttons"> +				{/* <MutationButton +					label="Disable" +					name="disable" +					result={result} +				/> +				<MutationButton +					label="Silence" +					name="silence" +					result={result} +				/> */} +				<MutationButton +					disabled={account.suspended || reallySuspend.value === undefined || reallySuspend.value === false} +					label="Suspend" +					name="suspend" +					result={result} +				/> +				<Checkbox +					label="Really suspend" +					field={reallySuspend} +				></Checkbox> +			</div> +		</form> +	); +} diff --git a/web/source/settings/admin/accounts/detail/handlesignup.tsx b/web/source/settings/admin/accounts/detail/handlesignup.tsx new file mode 100644 index 000000000..a61145a22 --- /dev/null +++ b/web/source/settings/admin/accounts/detail/handlesignup.tsx @@ -0,0 +1,118 @@ +/* +	GoToSocial +	Copyright (C) GoToSocial Authors admin@gotosocial.org +	SPDX-License-Identifier: AGPL-3.0-or-later + +	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 <http://www.gnu.org/licenses/>. +*/ + +import React from "react"; +import { useLocation } from "wouter"; + +import { useHandleSignupMutation } from "../../../lib/query"; + +import MutationButton from "../../../components/form/mutation-button"; + +import useFormSubmit from "../../../lib/form/submit"; +import { +	useValue, +	useTextInput, +	useBoolInput, +} from "../../../lib/form"; + +import { Checkbox, Select, TextInput } from "../../../components/form/inputs"; +import { AdminAccount } from "../../../lib/types/account"; + +export interface HandleSignupProps { +	account: AdminAccount, +	accountsBaseUrl: string, +} + +export function HandleSignup({account, accountsBaseUrl}: HandleSignupProps) { +	const form = { +		id: useValue("id", account.id), +		approveOrReject: useTextInput("approve_or_reject", { defaultValue: "approve" }), +		privateComment: useTextInput("private_comment"), +		message: useTextInput("message"), +		sendEmail: useBoolInput("send_email"), +	}; + +	const [_location, setLocation] = useLocation(); + +	const [handleSignup, result] = useFormSubmit(form, useHandleSignupMutation(), { +		changedOnly: false, +		// After submitting the form, redirect back to +		// /settings/admin/accounts if rejecting, since +		// account will no longer be available at +		// /settings/admin/accounts/:accountID endpoint. +		onFinish: (res) => { +			if (form.approveOrReject.value === "approve") { +				// An approve request: +				// stay on this page and +				// serve updated details. +				return; +			} +			 +			if (res.data) { +				// "reject" successful, +				// redirect to accounts page. +				setLocation(accountsBaseUrl); +			} +		} +	}); + +	return ( +		<form +			onSubmit={handleSignup} +			aria-labelledby="account-handle-signup" +		> +			<h3 id="account-handle-signup">Handle Account Sign-Up</h3> +			<Select +				field={form.approveOrReject} +				label="Approve or Reject" +				options={ +					<> +						<option value="approve">Approve</option> +						<option value="reject">Reject</option> +					</> +				} +			> +			</Select> +			{ form.approveOrReject.value === "reject" && +			// Only show form fields relevant +			// to "reject" if rejecting. +			// On "approve" these fields will +			// be ignored anyway. +			<> +				<TextInput +					field={form.privateComment} +					label="(Optional) private comment on why sign-up was rejected (shown to other admins only)" +				/> +				<Checkbox +					field={form.sendEmail} +					label="Send email to applicant" +				/> +				<TextInput +					field={form.message} +					label={"(Optional) message to include in email to applicant, if send email is checked"} +				/> +			</> } +			<MutationButton +				disabled={false} +				label={form.approveOrReject.value === "approve" ? "Approve" : "Reject"} +				result={result} +			/> +		</form> +	); +} diff --git a/web/source/settings/admin/accounts/detail/index.tsx b/web/source/settings/admin/accounts/detail/index.tsx new file mode 100644 index 000000000..79eb493de --- /dev/null +++ b/web/source/settings/admin/accounts/detail/index.tsx @@ -0,0 +1,179 @@ +/* +	GoToSocial +	Copyright (C) GoToSocial Authors admin@gotosocial.org +	SPDX-License-Identifier: AGPL-3.0-or-later + +	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 <http://www.gnu.org/licenses/>. +*/ + +import React from "react"; +import { useRoute, Redirect } from "wouter"; + +import { useGetAccountQuery } from "../../../lib/query"; + +import FormWithData from "../../../lib/form/form-with-data"; + +import { useBaseUrl } from "../../../lib/navigation/util"; +import FakeProfile from "../../../components/fake-profile"; + +import { AdminAccount } from "../../../lib/types/account"; +import { HandleSignup } from "./handlesignup"; +import { AccountActions } from "./actions"; +import BackButton from "../../../components/back-button"; + +export default function AccountDetail() { +	// /settings/admin/accounts +	const accountsBaseUrl = useBaseUrl(); + +	let [_match, params] = useRoute(`${accountsBaseUrl}/:accountId`); + +	if (params?.accountId == undefined) { +		return <Redirect to={accountsBaseUrl} />; +	} else { +		return ( +			<div className="account-detail"> +				<h1 className="text-cutoff"> +					<BackButton to={accountsBaseUrl} /> Account Details +				</h1> +				<FormWithData +					dataQuery={useGetAccountQuery} +					queryArg={params.accountId} +					DataForm={AccountDetailForm} +					{...{accountsBaseUrl}} +				/> +			</div> +		); +	} +} + +interface AccountDetailFormProps { +	accountsBaseUrl: string, +	data: AdminAccount, +} + +function AccountDetailForm({ data: adminAcct, accountsBaseUrl }: AccountDetailFormProps) {	 +	let yesOrNo = (b: boolean) => { +		return b ? "yes" : "no"; +	}; + +	let created = new Date(adminAcct.created_at).toDateString(); +	let lastPosted = "never"; +	if (adminAcct.account.last_status_at) { +		lastPosted = new Date(adminAcct.account.last_status_at).toDateString(); +	} +	const local = !adminAcct.domain; + +	return ( +		<> +			<FakeProfile {...adminAcct.account} /> +			<h3>General Account Details</h3> +			{ adminAcct.suspended && +				<div className="info"> +					<i className="fa fa-fw fa-info-circle" aria-hidden="true"></i> +					<b>Account is suspended.</b> +				</div> +			} +			<dl className="info-list"> +				{ !local && +				<div className="info-list-entry"> +					<dt>Domain</dt> +					<dd>{adminAcct.domain}</dd> +				</div>} +				<div className="info-list-entry"> +					<dt>Created</dt> +					<dd><time dateTime={adminAcct.created_at}>{created}</time></dd> +				</div> +				<div className="info-list-entry"> +					<dt>Last posted</dt> +					<dd>{lastPosted}</dd> +				</div> +				<div className="info-list-entry"> +					<dt>Suspended</dt> +					<dd>{yesOrNo(adminAcct.suspended)}</dd> +				</div> +				<div className="info-list-entry"> +					<dt>Silenced</dt> +					<dd>{yesOrNo(adminAcct.silenced)}</dd> +				</div> +				<div className="info-list-entry"> +					<dt>Statuses</dt> +					<dd>{adminAcct.account.statuses_count}</dd> +				</div> +				<div className="info-list-entry"> +					<dt>Followers</dt> +					<dd>{adminAcct.account.followers_count}</dd> +				</div> +				<div className="info-list-entry"> +					<dt>Following</dt> +					<dd>{adminAcct.account.following_count}</dd> +				</div> +			</dl> +			{ local && +			// Only show local account details +			// if this is a local account! +			<> +				<h3>Local Account Details</h3> +				{ !adminAcct.approved && +					<div className="info"> +						<i className="fa fa-fw fa-info-circle" aria-hidden="true"></i> +						<b>Account is pending.</b> +					</div> +				} +				{ !adminAcct.confirmed &&  +					<div className="info"> +						<i className="fa fa-fw fa-info-circle" aria-hidden="true"></i> +						<b>Account email not yet confirmed.</b> +					</div> +				} +				<dl className="info-list"> +					<div className="info-list-entry"> +						<dt>Email</dt> +						<dd>{adminAcct.email} {<b>{adminAcct.confirmed ? "(confirmed)" : "(not confirmed)"}</b> }</dd> +					</div> +					<div className="info-list-entry"> +						<dt>Disabled</dt> +						<dd>{yesOrNo(adminAcct.disabled)}</dd> +					</div> +					<div className="info-list-entry"> +						<dt>Approved</dt> +						<dd>{yesOrNo(adminAcct.approved)}</dd> +					</div> +					<div className="info-list-entry"> +						<dt>Sign-Up Reason</dt> +						<dd>{adminAcct.invite_request ?? <i>none provided</i>}</dd> +					</div> +					{ (adminAcct.ip && adminAcct.ip !== "0.0.0.0") && +					<div className="info-list-entry"> +						<dt>Sign-Up IP</dt> +						<dd>{adminAcct.ip}</dd> +					</div> } +					{ adminAcct.locale && +					<div className="info-list-entry"> +						<dt>Locale</dt> +						<dd>{adminAcct.locale}</dd> +					</div> } +				</dl> +			</> } +			{ local && !adminAcct.approved +				? +				<HandleSignup +					account={adminAcct} +					accountsBaseUrl={accountsBaseUrl} +				/> +				: +				<AccountActions account={adminAcct} /> +			} +		</> +	); +} | 
