diff options
Diffstat (limited to 'web/source/settings/views')
12 files changed, 360 insertions, 292 deletions
| diff --git a/web/source/settings/views/moderation/accounts/detail/actions.tsx b/web/source/settings/views/moderation/accounts/detail/actions.tsx index 212bb4089..4132b778a 100644 --- a/web/source/settings/views/moderation/accounts/detail/actions.tsx +++ b/web/source/settings/views/moderation/accounts/detail/actions.tsx @@ -19,7 +19,7 @@  import React from "react"; -import { useActionAccountMutation } from "../../../../lib/query/admin"; +import { useActionAccountMutation, useHandleSignupMutation } from "../../../../lib/query/admin";  import MutationButton from "../../../../components/form/mutation-button";  import useFormSubmit from "../../../../lib/form/submit";  import { @@ -27,22 +27,50 @@ import {  	useTextInput,  	useBoolInput,  } from "../../../../lib/form"; -import { Checkbox, TextInput } from "../../../../components/form/inputs"; +import { Checkbox, Select, TextInput } from "../../../../components/form/inputs";  import { AdminAccount } from "../../../../lib/types/account"; +import { useLocation } from "wouter";  export interface AccountActionsProps {  	account: AdminAccount, +	backLocation: string,  } -export function AccountActions({ account }: AccountActionsProps) { +export function AccountActions({ account, backLocation }: AccountActionsProps) { +	const local = !account.domain; +	 +	// Available actions differ depending +	// on the account's current status. +	switch (true) { +		case account.suspended: +			// Can't do anything with +			// suspended accounts currently. +			return null; +		case local && !account.approved: +			// Unapproved local account sign-up, +			// only show HandleSignup form. +			return ( +				<HandleSignup +					account={account} +					backLocation={backLocation} +				/> +			); +		default: +			// Normal local or remote account, show +			// full range of moderation options. +			return <ModerateAccount account={account} />; +	} +} + +function ModerateAccount({ account }: { account: AdminAccount }) {  	const form = {  		id: useValue("id", account.id),  		reason: useTextInput("text")  	}; - +	  	const reallySuspend = useBoolInput("reallySuspend");  	const [accountAction, result] = useFormSubmit(form, useActionAccountMutation()); - +	  	return (  		<form  			onSubmit={accountAction} @@ -60,16 +88,6 @@ export function AccountActions({ account }: AccountActionsProps) {  				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" @@ -84,3 +102,81 @@ export function AccountActions({ account }: AccountActionsProps) {  		</form>  	);  } + +function HandleSignup({ account, backLocation }: { account: AdminAccount, backLocation: string }) { +	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(backLocation); +			} +		} +	}); + +	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/views/moderation/accounts/detail/handlesignup.tsx b/web/source/settings/views/moderation/accounts/detail/handlesignup.tsx deleted file mode 100644 index 59fa8bc65..000000000 --- a/web/source/settings/views/moderation/accounts/detail/handlesignup.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/* -	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/admin"; -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, -	backLocation: string, -} - -export function HandleSignup({account, backLocation}: 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(backLocation); -			} -		} -	}); - -	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/views/moderation/accounts/detail/index.tsx b/web/source/settings/views/moderation/accounts/detail/index.tsx index f34bc7481..830a894cb 100644 --- a/web/source/settings/views/moderation/accounts/detail/index.tsx +++ b/web/source/settings/views/moderation/accounts/detail/index.tsx @@ -23,51 +23,89 @@ import { useGetAccountQuery } from "../../../../lib/query/admin";  import FormWithData from "../../../../lib/form/form-with-data";  import FakeProfile from "../../../../components/fake-profile";  import { AdminAccount } from "../../../../lib/types/account"; -import { HandleSignup } from "./handlesignup";  import { AccountActions } from "./actions";  import { useParams } from "wouter"; +import { useBaseUrl } from "../../../../lib/navigation/util"; +import BackButton from "../../../../components/back-button"; +import { UseOurInstanceAccount, yesOrNo } from "./util";  export default function AccountDetail() {  	const params: { accountID: string } = useParams(); -	 +	const baseUrl = useBaseUrl(); +	const backLocation: String = history.state?.backLocation ?? `~${baseUrl}`; +  	return (  		<div className="account-detail"> -			<h1>Account Details</h1> +			<h1><BackButton to={backLocation} /> Account Details</h1>  			<FormWithData  				dataQuery={useGetAccountQuery}  				queryArg={params.accountID}  				DataForm={AccountDetailForm} +				{...{ backLocation: backLocation }}  			/>  		</div>  	);  }  interface AccountDetailFormProps { -	backLocation: string, -	data: AdminAccount, +	data: AdminAccount; +	backLocation: string;  } -function AccountDetailForm({ data: adminAcct, backLocation }: AccountDetailFormProps) {	 -	let yesOrNo = (b: boolean) => { -		return b ? "yes" : "no"; -	}; +function AccountDetailForm({ data: adminAcct, backLocation }: AccountDetailFormProps) { +	// If this is our instance account, don't +	// bother returning detailed account information. +	const ourInstanceAccount = UseOurInstanceAccount(adminAcct); +	if (ourInstanceAccount) { +		return ( +			<> +				<FakeProfile {...adminAcct.account} /> +				<div className="info"> +					<i className="fa fa-fw fa-info-circle" aria-hidden="true"></i> +					<b> +						This is the service account for your instance; you +						cannot perform moderation actions on this account. +					</b> +				</div> +			</> +		); +	} + +	const local = !adminAcct.domain; +	return ( +		<> +			<FakeProfile {...adminAcct.account} /> +			<GeneralAccountDetails adminAcct={adminAcct} /> +			{ +				// Only show local account details +				// if this is a local account! +				local && <LocalAccountDetails adminAcct={adminAcct} /> +			} +			<AccountActions +				account={adminAcct} +				backLocation={backLocation} +			/> +		</> +	); +} -	let created = new Date(adminAcct.created_at).toDateString(); +function GeneralAccountDetails({ adminAcct } : { adminAcct: AdminAccount }) { +	const local = !adminAcct.domain; +	const 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> +			{ 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 && @@ -76,6 +114,18 @@ function AccountDetailForm({ data: adminAcct, backLocation }: AccountDetailFormP  					<dd>{adminAcct.domain}</dd>  				</div>}  				<div className="info-list-entry"> +					<dt>Profile URL</dt> +					<dd> +						<a +							href={adminAcct.account.url} +							target="_blank" +							rel="noreferrer" +						> +							<i className="fa fa-fw fa-external-link" aria-hidden="true"></i> {adminAcct.account.url} (opens in a new tab) +						</a>  +					</dd> +				</div> +				<div className="info-list-entry">  					<dt>Created</dt>  					<dd><time dateTime={adminAcct.created_at}>{created}</time></dd>  				</div> @@ -104,61 +154,54 @@ function AccountDetailForm({ data: adminAcct, backLocation }: AccountDetailFormP  					<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 && +		</> +	); +} + +function LocalAccountDetails({ adminAcct }: { adminAcct: AdminAccount }) {	 +	return ( +		<> +			<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 &&  +			} +			{ !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") && +			} +			<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 && +				{ adminAcct.locale &&  					<div className="info-list-entry">  						<dt>Locale</dt>  						<dd>{adminAcct.locale}</dd>  					</div> } -				</dl> -			</> } -			{ local && !adminAcct.approved -				? -				<HandleSignup -					account={adminAcct} -					backLocation={backLocation} -				/> -				: -				<AccountActions account={adminAcct} /> -			} -		</> +			</dl> +		</>   	);  } diff --git a/web/source/settings/views/moderation/accounts/detail/util.tsx b/web/source/settings/views/moderation/accounts/detail/util.tsx new file mode 100644 index 000000000..b82d44a6e --- /dev/null +++ b/web/source/settings/views/moderation/accounts/detail/util.tsx @@ -0,0 +1,43 @@ +/* +	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 { useMemo } from "react"; + +import { AdminAccount } from "../../../../lib/types/account"; +import { store } from "../../../../redux/store"; + +export function yesOrNo(b: boolean): string { +	return b ? "yes" : "no"; +} + +export function UseOurInstanceAccount(account: AdminAccount): boolean { +	// Pull our own URL out of storage so we can +	// tell if account is our instance account. +	const ourDomain = useMemo(() => { +		const instanceUrlStr = store.getState().oauth.instanceUrl; +		if (!instanceUrlStr) { +			return ""; +		} + +		const instanceUrl = new URL(instanceUrlStr); +		return instanceUrl.host; +	}, []); + +	return !account.domain && account.username == ourDomain; +} diff --git a/web/source/settings/views/moderation/accounts/index.tsx b/web/source/settings/views/moderation/accounts/index.tsx index 79ba2c674..946ed323d 100644 --- a/web/source/settings/views/moderation/accounts/index.tsx +++ b/web/source/settings/views/moderation/accounts/index.tsx @@ -20,10 +20,10 @@  import React from "react";  import { AccountSearchForm } from "./search"; -export default function AccountsOverview({ }) { +export default function AccountsSearch({ }) {  	return (  		<div className="accounts-view"> -			<h1>Accounts Overview</h1> +			<h1>Accounts Search</h1>  			<span>  				You can perform actions on an account by clicking  				its name in a report, or by searching for the account diff --git a/web/source/settings/views/moderation/accounts/pending/index.tsx b/web/source/settings/views/moderation/accounts/pending/index.tsx index d5a32f09b..b72de52bf 100644 --- a/web/source/settings/views/moderation/accounts/pending/index.tsx +++ b/web/source/settings/views/moderation/accounts/pending/index.tsx @@ -17,20 +17,40 @@  	along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -import React from "react"; +import React, { ReactNode } from "react";  import { useSearchAccountsQuery } from "../../../../lib/query/admin"; -import { AccountList } from "../../../../components/account-list"; +import { PageableList } from "../../../../components/pageable-list"; +import { useLocation } from "wouter"; +import Username from "../../../../components/username"; +import { AdminAccount } from "../../../../lib/types/account";  export default function AccountsPending() { +	const [ location, _setLocation ] = useLocation();  	const searchRes = useSearchAccountsQuery({status: "pending"}); +	// Function to map an item to a list entry. +	function itemToEntry(account: AdminAccount): ReactNode { +		const acc = account.account; +		return ( +			<Username +				key={acc.acct} +				account={account} +				linkTo={`/${account.id}`} +				backLocation={location} +				classNames={["entry"]} +			/> +		); +	} +  	return (  		<div className="accounts-view">  			<h1>Pending Accounts</h1> -			<AccountList +			<PageableList  				isLoading={searchRes.isLoading} +				isFetching={searchRes.isFetching}  				isSuccess={searchRes.isSuccess} -				data={searchRes.data} +				items={searchRes.data?.accounts} +				itemToEntry={itemToEntry}  				isError={searchRes.isError}  				error={searchRes.error}  				emptyMessage="No pending account sign-ups." diff --git a/web/source/settings/views/moderation/accounts/search/index.tsx b/web/source/settings/views/moderation/accounts/search/index.tsx index 8ee579e16..58e2d6505 100644 --- a/web/source/settings/views/moderation/accounts/search/index.tsx +++ b/web/source/settings/views/moderation/accounts/search/index.tsx @@ -17,28 +17,53 @@  	along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -import React from "react"; +import React, { ReactNode, useEffect, useMemo } from "react";  import { useLazySearchAccountsQuery } from "../../../../lib/query/admin";  import { useTextInput } from "../../../../lib/form"; -import { AccountList } from "../../../../components/account-list"; -import { SearchAccountParams } from "../../../../lib/types/account"; +import { PageableList } from "../../../../components/pageable-list";  import { Select, TextInput } from "../../../../components/form/inputs";  import MutationButton from "../../../../components/form/mutation-button"; +import { useLocation, useSearch } from "wouter"; +import { AdminAccount } from "../../../../lib/types/account"; +import Username from "../../../../components/username";  export function AccountSearchForm() { +	const [ location, setLocation ] = useLocation(); +	const search = useSearch(); +	const urlQueryParams = useMemo(() => new URLSearchParams(search), [search]); +	const [ searchAcct, searchRes ] = useLazySearchAccountsQuery(); + +	// Populate search form using values from +	// urlQueryParams, to allow paging.  	const form = { -		origin: useTextInput("origin"), -		status: useTextInput("status"), -		permissions: useTextInput("permissions"), -		username: useTextInput("username"), -		display_name: useTextInput("display_name"), -		by_domain: useTextInput("by_domain"), -		email: useTextInput("email"), -		ip: useTextInput("ip"), +		origin: useTextInput("origin", { defaultValue: urlQueryParams.get("origin") ?? ""}), +		status: useTextInput("status", { defaultValue: urlQueryParams.get("status") ?? ""}), +		permissions: useTextInput("permissions", { defaultValue: urlQueryParams.get("permissions") ?? ""}), +		username: useTextInput("username", { defaultValue: urlQueryParams.get("username") ?? ""}), +		display_name: useTextInput("display_name", { defaultValue: urlQueryParams.get("display_name") ?? ""}), +		by_domain: useTextInput("by_domain", { defaultValue: urlQueryParams.get("by_domain") ?? ""}), +		email: useTextInput("email", { defaultValue: urlQueryParams.get("email") ?? ""}), +		ip: useTextInput("ip", { defaultValue: urlQueryParams.get("ip") ?? ""}), +		limit: useTextInput("limit", { defaultValue: urlQueryParams.get("limit") ?? "50"})  	}; -	function submitSearch(e) { +	// On mount, if urlQueryParams were provided, +	// trigger the search. For example, if page +	// was accessed at /search?origin=local&limit=20, +	// then run a search with origin=local and +	// limit=20 and immediately render the results. +	useEffect(() => { +		if (urlQueryParams.size > 0) { +			searchAcct(Object.fromEntries(urlQueryParams), true); +		} +	}, [urlQueryParams, searchAcct]); + +	// Rather than triggering the search directly, +	// the "submit" button changes the location +	// based on form field params, and lets the +	// useEffect hook above actually do the search. +	function submitQuery(e) {  		e.preventDefault();  		// Parse query parameters. @@ -52,16 +77,32 @@ export function AccountSearchForm() {  			// Remove any nulls.  			return kv || [];  		}); -		const params: SearchAccountParams = Object.fromEntries(entries); -		searchAcct(params); + +		const searchParams = new URLSearchParams(entries); +		setLocation(location + "?" + searchParams.toString());  	} -	const [ searchAcct, searchRes ] = useLazySearchAccountsQuery(); +	// Location to return to when user clicks "back" on the account detail view. +	const backLocation = location + (urlQueryParams ? `?${urlQueryParams}` : ""); +	 +	// Function to map an item to a list entry. +	function itemToEntry(account: AdminAccount): ReactNode { +		const acc = account.account; +		return ( +			<Username +				key={acc.acct} +				account={account} +				linkTo={`/${account.id}`} +				backLocation={backLocation} +				classNames={["entry"]} +			/> +		); +	}  	return (  		<>  			<form -				onSubmit={submitSearch} +				onSubmit={submitQuery}  				// Prevent password managers trying  				// to fill in username/email fields.  				autoComplete="off" @@ -117,13 +158,16 @@ export function AccountSearchForm() {  					result={searchRes}  				/>  			</form> -			<AccountList +			<PageableList  				isLoading={searchRes.isLoading} +				isFetching={searchRes.isFetching}  				isSuccess={searchRes.isSuccess} -				data={searchRes.data} +				items={searchRes.data?.accounts} +				itemToEntry={itemToEntry}  				isError={searchRes.isError}  				error={searchRes.error}  				emptyMessage="No accounts found that match your query" +				prevNextLinks={searchRes.data?.links}  			/>  		</>  	); diff --git a/web/source/settings/views/moderation/menu.tsx b/web/source/settings/views/moderation/menu.tsx index 4f01e0798..9488b8c30 100644 --- a/web/source/settings/views/moderation/menu.tsx +++ b/web/source/settings/views/moderation/menu.tsx @@ -28,7 +28,7 @@ import { useHasPermission } from "../../lib/navigation/util";  /**   * - /settings/moderation/reports/overview   * - /settings/moderation/reports/:reportId - * - /settings/moderation/accounts/overview + * - /settings/moderation/accounts/search   * - /settings/moderation/accounts/pending   * - /settings/moderation/accounts/:accountID   * - /settings/moderation/domain-permissions/:permType @@ -76,12 +76,12 @@ function ModerationAccountsMenu() {  		<MenuItem  			name="Accounts"  			itemUrl="accounts" -			defaultChild="overview" +			defaultChild="search"  			icon="fa-users"  		>  			<MenuItem -				name="Overview" -				itemUrl="overview" +				name="Search" +				itemUrl="search"  				icon="fa-list"  			/>  			<MenuItem diff --git a/web/source/settings/views/moderation/reports/detail.tsx b/web/source/settings/views/moderation/reports/detail.tsx index bc356edce..ad8d69a47 100644 --- a/web/source/settings/views/moderation/reports/detail.tsx +++ b/web/source/settings/views/moderation/reports/detail.tsx @@ -25,7 +25,7 @@ import { useValue, useTextInput } from "../../../lib/form";  import useFormSubmit from "../../../lib/form/submit";  import { TextArea } from "../../../components/form/inputs";  import MutationButton from "../../../components/form/mutation-button"; -import Username from "./username"; +import Username from "../../../components/username";  import { useGetReportQuery, useResolveReportMutation } from "../../../lib/query/admin/reports";  import { useBaseUrl } from "../../../lib/navigation/util"; @@ -53,13 +53,15 @@ function ReportDetailForm({ data: report }) {  		<div className="report detail">  			<div className="usernames">  				<Username -					user={from} -					link={`~/settings/moderation/accounts/${from.id}`} +					account={from} +					linkTo={`~/settings/moderation/accounts/${from.id}`} +					backLocation={`~/settings/moderation/reports/${report.id}`}  				/>  				<> reported </>  				<Username -					user={target} -					link={`~/settings/moderation/accounts/${target.id}`} +					account={target} +					linkTo={`~/settings/moderation/accounts/${target.id}`} +					backLocation={`~/settings/moderation/reports/${report.id}`}  				/>  			</div> diff --git a/web/source/settings/views/moderation/reports/overview.tsx b/web/source/settings/views/moderation/reports/overview.tsx index 03ce1a382..18eb5492a 100644 --- a/web/source/settings/views/moderation/reports/overview.tsx +++ b/web/source/settings/views/moderation/reports/overview.tsx @@ -20,7 +20,7 @@  import React from "react";  import { Link } from "wouter";  import FormWithData from "../../../lib/form/form-with-data"; -import Username from "./username"; +import Username from "../../../components/username";  import { useListReportsQuery } from "../../../lib/query/admin/reports";  export function ReportOverview({ }) { @@ -75,7 +75,7 @@ function ReportEntry({ report }) {  			<div className={`report entry${report.action_taken ? " resolved" : ""}`}>  				<div className="byline">  					<div className="usernames"> -						<Username user={from} /> reported <Username user={target} /> +						<Username account={from} /> reported <Username account={target} />  					</div>  					<h3 className="report-status">  						{report.action_taken ? "Resolved" : "Open"} diff --git a/web/source/settings/views/moderation/reports/username.tsx b/web/source/settings/views/moderation/reports/username.tsx deleted file mode 100644 index 294d97e8b..000000000 --- a/web/source/settings/views/moderation/reports/username.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* -	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 { Link } from "wouter"; -import { AdminAccount } from "../../../lib/types/account"; - -interface UsernameProps { -	user: AdminAccount; -	link?: string; -} - -export default function Username({ user, link }: UsernameProps) { -	let className = "user"; -	let isLocal = user.domain == null; - -	if (user.suspended) { -		className += " suspended"; -	} - -	if (isLocal) { -		className += " local"; -	} - -	let icon = isLocal -		? { fa: "fa-home", info: "Local user" } -		: { fa: "fa-external-link-square", info: "Remote user" }; - -	const content = ( -		<> -			<span className="acct">@{user.account.acct}</span> -			<i className={`fa fa-fw ${icon.fa}`} aria-hidden="true" title={icon.info} /> -			<span className="sr-only">{icon.info}</span> -		</> -	); - -	if (link) { -		return ( -			<Link className={className} to={link}> -				{content} -			</Link> -		); -	} else { -		return ( -			<div className={className}> -				{content} -			</div> -		); -	} -} diff --git a/web/source/settings/views/moderation/router.tsx b/web/source/settings/views/moderation/router.tsx index 37344462b..d23ab336a 100644 --- a/web/source/settings/views/moderation/router.tsx +++ b/web/source/settings/views/moderation/router.tsx @@ -26,7 +26,7 @@ import { ErrorBoundary } from "../../lib/navigation/error";  import ImportExport from "./domain-permissions/import-export";  import DomainPermissionsOverview from "./domain-permissions/overview";  import DomainPermDetail from "./domain-permissions/detail"; -import AccountsOverview from "./accounts"; +import AccountsSearch from "./accounts";  import AccountsPending from "./accounts/pending";  import AccountDetail from "./accounts/detail"; @@ -37,7 +37,7 @@ import AccountDetail from "./accounts/detail";  /**   * - /settings/moderation/reports/overview   * - /settings/moderation/reports/:reportId - * - /settings/moderation/accounts/overview + * - /settings/moderation/accounts/search   * - /settings/moderation/accounts/pending   * - /settings/moderation/accounts/:accountID   * - /settings/moderation/domain-permissions/:permType @@ -95,7 +95,7 @@ function ModerationReportsRouter() {  }  /** - * - /settings/moderation/accounts/overview + * - /settings/moderation/accounts/search   * - /settings/moderation/accounts/pending   * - /settings/moderation/accounts/:accountID   */ @@ -109,10 +109,10 @@ function ModerationAccountsRouter() {  			<Router base={thisBase}>  				<ErrorBoundary>  					<Switch> -						<Route path="/overview" component={AccountsOverview}/> +						<Route path="/search" component={AccountsSearch}/>  						<Route path="/pending" component={AccountsPending}/>  						<Route path="/:accountID" component={AccountDetail}/> -						<Route><Redirect to="/overview"/></Route> +						<Route><Redirect to="/search"/></Route>  					</Switch>  				</ErrorBoundary>  			</Router> | 
