diff options
Diffstat (limited to 'web/source/settings/admin/accounts')
| -rw-r--r-- | web/source/settings/admin/accounts/detail.jsx | 112 | ||||
| -rw-r--r-- | web/source/settings/admin/accounts/detail/actions.tsx | 89 | ||||
| -rw-r--r-- | web/source/settings/admin/accounts/detail/handlesignup.tsx | 118 | ||||
| -rw-r--r-- | web/source/settings/admin/accounts/detail/index.tsx | 179 | ||||
| -rw-r--r-- | web/source/settings/admin/accounts/index.jsx | 138 | ||||
| -rw-r--r-- | web/source/settings/admin/accounts/index.tsx | 49 | ||||
| -rw-r--r-- | web/source/settings/admin/accounts/pending/index.tsx | 40 | ||||
| -rw-r--r-- | web/source/settings/admin/accounts/search/index.tsx | 125 | 
8 files changed, 600 insertions, 250 deletions
| diff --git a/web/source/settings/admin/accounts/detail.jsx b/web/source/settings/admin/accounts/detail.jsx deleted file mode 100644 index 63049c149..000000000 --- a/web/source/settings/admin/accounts/detail.jsx +++ /dev/null @@ -1,112 +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/>. -*/ - -const React = require("react"); -const { useRoute, Redirect } = require("wouter"); - -const query = require("../../lib/query"); - -const FormWithData = require("../../lib/form/form-with-data").default; - -const { useBaseUrl } = require("../../lib/navigation/util"); -const FakeProfile = require("../../components/fake-profile"); -const MutationButton = require("../../components/form/mutation-button"); - -const useFormSubmit = require("../../lib/form/submit").default; -const { useValue, useTextInput } = require("../../lib/form"); -const { TextInput } = require("../../components/form/inputs"); - -module.exports = function AccountDetail({ }) { -	const baseUrl = useBaseUrl(); - -	let [_match, params] = useRoute(`${baseUrl}/:accountId`); - -	if (params?.accountId == undefined) { -		return <Redirect to={baseUrl} />; -	} else { -		return ( -			<div className="account-detail"> -				<h1> -					Account Details -				</h1> -				<FormWithData -					dataQuery={query.useGetAccountQuery} -					queryArg={params.accountId} -					DataForm={AccountDetailForm} -				/> -			</div> -		); -	} -}; - -function AccountDetailForm({ data: account }) { -	let content; -	if (account.suspended) { -		content = ( -			<h2 className="error">Account is suspended.</h2> -		); -	} else { -		content = <ModifyAccount account={account} />; -	} - -	return ( -		<> -			<FakeProfile {...account} /> - -			{content} -		</> -	); -} - -function ModifyAccount({ account }) { -	const form = { -		id: useValue("id", account.id), -		reason: useTextInput("text") -	}; - -	const [modifyAccount, result] = useFormSubmit(form, query.useActionAccountMutation()); - -	return ( -		<form onSubmit={modifyAccount}> -			<h2>Actions</h2> -			<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 -					label="Suspend" -					name="suspend" -					result={result} -				/> -			</div> -		</form> -	); -}
\ No newline at end of file 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} /> +			} +		</> +	); +} diff --git a/web/source/settings/admin/accounts/index.jsx b/web/source/settings/admin/accounts/index.jsx deleted file mode 100644 index c642d903e..000000000 --- a/web/source/settings/admin/accounts/index.jsx +++ /dev/null @@ -1,138 +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/>. -*/ - -const React = require("react"); -const { Switch, Route, Link } = require("wouter"); - -const query = require("../../lib/query"); -const { useTextInput } = require("../../lib/form"); - -const AccountDetail = require("./detail"); -const { useBaseUrl } = require("../../lib/navigation/util"); -const { Error } = require("../../components/error"); - -module.exports = function Accounts({ baseUrl }) { -	return ( -		<div className="accounts"> -			<Switch> -				<Route path={`${baseUrl}/:accountId`}> -					<AccountDetail /> -				</Route> -				<AccountOverview /> -			</Switch> -		</div> -	); -}; - -function AccountOverview({ }) { -	return ( -		<> -			<h1>Accounts</h1> -			<div> -				Pending <a href="https://github.com/superseriousbusiness/gotosocial/issues/581">#581</a>, -				there is currently no way to list accounts.<br /> -				You can perform actions on reported accounts by clicking their name in the report, or searching for a username below. -			</div> - -			<AccountSearchForm /> -		</> -	); -} - -function AccountSearchForm() { -	const [searchAccount, result] = query.useSearchAccountMutation(); - -	const [onAccountChange, _resetAccount, { account }] = useTextInput("account"); - -	function submitSearch(e) { -		e.preventDefault(); -		if (account.trim().length != 0) { -			searchAccount(account); -		} -	} - -	return ( -		<div className="account-search"> -			<form onSubmit={submitSearch}> -				<div className="form-field text"> -					<label htmlFor="url"> -						Account: -					</label> -					<div className="row"> -						<input -							type="text" -							id="account" -							name="account" -							onChange={onAccountChange} -							value={account} -						/> -						<button disabled={result.isLoading}> -							<i className={[ -								"fa fa-fw", -								(result.isLoading -									? "fa-refresh fa-spin" -									: "fa-search") -							].join(" ")} aria-hidden="true" title="Search" /> -							<span className="sr-only">Search</span> -						</button> -					</div> -				</div> -			</form> -			<AccountList -				isSuccess={result.isSuccess} -				data={result.data} -				isError={result.isError} -				error={result.error} -			/> -		</div> -	); -} - -function AccountList({ isSuccess, data, isError, error }) { -	const baseUrl = useBaseUrl(); - -	if (!(isSuccess || isError)) { -		return null; -	} - -	if (error) { -		return <Error error={error} />; -	} - -	if (data.length == 0) { -		return <b>No accounts found that match your query</b>; -	} - -	return ( -		<> -			<h2>Results:</h2> -			<div className="list"> -				{data.map((acc) => ( -					<Link key={acc.acct} className="account entry" to={`${baseUrl}/${acc.id}`}> -						{acc.display_name?.length > 0 -							? acc.display_name -							: acc.username -						} -						<span id="username">(@{acc.acct})</span> -					</Link> -				))} -			</div> -		</> -	); -}
\ No newline at end of file diff --git a/web/source/settings/admin/accounts/index.tsx b/web/source/settings/admin/accounts/index.tsx new file mode 100644 index 000000000..3c69f7406 --- /dev/null +++ b/web/source/settings/admin/accounts/index.tsx @@ -0,0 +1,49 @@ +/* +	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 { Switch, Route } from "wouter"; + +import AccountDetail from "./detail"; +import { AccountSearchForm } from "./search"; + +export default function Accounts({ baseUrl }) { +	return ( +		<Switch> +			<Route path={`${baseUrl}/:accountId`}> +				<AccountDetail /> +			</Route> +			<AccountOverview /> +		</Switch> +	); +} + +function AccountOverview({ }) { +	return ( +		<div className="accounts-view"> +			<h1>Accounts Overview</h1> +			<span> +				You can perform actions on an account by clicking +				its name in a report, or by searching for the account +				using the form below and clicking on its name. +			</span> +			<AccountSearchForm /> +		</div> +	); +} diff --git a/web/source/settings/admin/accounts/pending/index.tsx b/web/source/settings/admin/accounts/pending/index.tsx new file mode 100644 index 000000000..459472147 --- /dev/null +++ b/web/source/settings/admin/accounts/pending/index.tsx @@ -0,0 +1,40 @@ +/* +	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 { useSearchAccountsQuery } from "../../../lib/query"; +import { AccountList } from "../../../components/account-list"; + +export default function AccountsPending() { +	const searchRes = useSearchAccountsQuery({status: "pending"}); + +	return ( +		<div className="accounts-view"> +			<h1>Pending Accounts</h1> +			<AccountList +				isLoading={searchRes.isLoading} +				isSuccess={searchRes.isSuccess} +				data={searchRes.data} +				isError={searchRes.isError} +				error={searchRes.error} +				emptyMessage="No pending account sign-ups." +			/> +		</div> +	); +} diff --git a/web/source/settings/admin/accounts/search/index.tsx b/web/source/settings/admin/accounts/search/index.tsx new file mode 100644 index 000000000..560bbb76a --- /dev/null +++ b/web/source/settings/admin/accounts/search/index.tsx @@ -0,0 +1,125 @@ +/* +	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 { useLazySearchAccountsQuery } from "../../../lib/query"; +import { useTextInput } from "../../../lib/form"; + +import { AccountList } from "../../../components/account-list"; +import { SearchAccountParams } from "../../../lib/types/account"; +import { Select, TextInput } from "../../../components/form/inputs"; +import MutationButton from "../../../components/form/mutation-button"; + +export function AccountSearchForm() { +	const [searchAcct, searchRes] = useLazySearchAccountsQuery(); + +	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"), +	}; + +	function submitSearch(e) { +		e.preventDefault(); +		 +		// Parse query parameters. +		const entries = Object.entries(form).map(([k, v]) => { +			// Take only defined form fields. +			if (v.value === undefined || v.value.length === 0) { +				return null; +			} +			return [[k, v.value]]; +		}).flatMap(kv => { +			// Remove any nulls. +			return kv || []; +		}); + +		const params: SearchAccountParams = Object.fromEntries(entries); +		searchAcct(params); +	} + +	return ( +		<> +			<form onSubmit={submitSearch}> +				<TextInput +					field={form.username} +					label={"(Optional) username (without leading '@' symbol)"} +					placeholder="someone" +				/> +				<TextInput +					field={form.by_domain} +					label={"(Optional) domain"} +					placeholder="example.org" +				/> +				<Select +					field={form.origin} +					label="Account origin" +					options={ +						<> +							<option value="">Local or remote</option> +							<option value="local">Local only</option> +							<option value="remote">Remote only</option> +						</> +					} +				></Select> +				<TextInput +					field={form.email} +					label={"(Optional) email address (local accounts only)"} +					placeholder={"someone@example.org"} +				/> +				<TextInput +					field={form.ip} +					label={"(Optional) IP address (local accounts only)"} +					placeholder={"198.51.100.0"} +				/> +				<Select +					field={form.status} +					label="Account status" +					options={ +						<> +							<option value="">Any</option> +							<option value="pending">Pending only</option> +							<option value="disabled">Disabled only</option> +							<option value="suspended">Suspended only</option> +						</> +					} +				></Select> +				<MutationButton +					disabled={false} +					label={"Search"} +					result={searchRes} +				/> +			</form> +			<AccountList +				isLoading={searchRes.isLoading} +				isSuccess={searchRes.isSuccess} +				data={searchRes.data} +				isError={searchRes.isError} +				error={searchRes.error} +				emptyMessage="No accounts found that match your query" +			/> +		</> +	); +} | 
