diff options
Diffstat (limited to 'web/source/settings/admin')
23 files changed, 807 insertions, 432 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" + /> + </> + ); +} diff --git a/web/source/settings/admin/actions/keys/expireremote.jsx b/web/source/settings/admin/actions/keys/expireremote.tsx index 172f65bc3..3b5da2836 100644 --- a/web/source/settings/admin/actions/keys/expireremote.jsx +++ b/web/source/settings/admin/actions/keys/expireremote.tsx @@ -17,19 +17,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -const React = require("react"); +import React from "react"; -const query = require("../../../lib/query"); +import { useInstanceKeysExpireMutation } from "../../../lib/query"; -const { useTextInput } = require("../../../lib/form"); -const { TextInput } = require("../../../components/form/inputs"); +import { useTextInput } from "../../../lib/form"; +import { TextInput } from "../../../components/form/inputs"; -const MutationButton = require("../../../components/form/mutation-button"); +import MutationButton from "../../../components/form/mutation-button"; -module.exports = function ExpireRemote({}) { +export default function ExpireRemote({}) { const domainField = useTextInput("domain"); - const [expire, expireResult] = query.useInstanceKeysExpireMutation(); + const [expire, expireResult] = useInstanceKeysExpireMutation(); function submitExpire(e) { e.preventDefault(); @@ -53,7 +53,11 @@ module.exports = function ExpireRemote({}) { type="string" placeholder="example.org" /> - <MutationButton label="Expire keys" result={expireResult} /> + <MutationButton + disabled={false} + label="Expire keys" + result={expireResult} + /> </form> ); -}; +} diff --git a/web/source/settings/admin/actions/keys/index.jsx b/web/source/settings/admin/actions/keys/index.tsx index f6a851e70..74bfd36ee 100644 --- a/web/source/settings/admin/actions/keys/index.jsx +++ b/web/source/settings/admin/actions/keys/index.tsx @@ -17,14 +17,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -const React = require("react"); -const ExpireRemote = require("./expireremote"); +import React from "react"; +import ExpireRemote from "./expireremote"; -module.exports = function Keys() { +export default function Keys() { return ( <> <h1>Key Actions</h1> <ExpireRemote /> </> ); -}; +} diff --git a/web/source/settings/admin/actions/media/cleanup.jsx b/web/source/settings/admin/actions/media/cleanup.tsx index 8b0e628f6..fd3ca1f41 100644 --- a/web/source/settings/admin/actions/media/cleanup.jsx +++ b/web/source/settings/admin/actions/media/cleanup.tsx @@ -17,19 +17,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -const React = require("react"); +import React from "react"; -const query = require("../../../lib/query"); +import { useMediaCleanupMutation } from "../../../lib/query"; -const { useTextInput } = require("../../../lib/form"); -const { TextInput } = require("../../../components/form/inputs"); +import { useTextInput } from "../../../lib/form"; +import { TextInput } from "../../../components/form/inputs"; -const MutationButton = require("../../../components/form/mutation-button"); +import MutationButton from "../../../components/form/mutation-button"; -module.exports = function Cleanup({}) { - const daysField = useTextInput("days", { defaultValue: 30 }); +export default function Cleanup({}) { + const daysField = useTextInput("days", { defaultValue: "30" }); - const [mediaCleanup, mediaCleanupResult] = query.useMediaCleanupMutation(); + const [mediaCleanup, mediaCleanupResult] = useMediaCleanupMutation(); function submitCleanup(e) { e.preventDefault(); @@ -51,7 +51,11 @@ module.exports = function Cleanup({}) { min="0" placeholder="30" /> - <MutationButton label="Remove old media" result={mediaCleanupResult} /> + <MutationButton + disabled={false} + label="Remove old media" + result={mediaCleanupResult} + /> </form> ); -}; +} diff --git a/web/source/settings/admin/actions/media/index.jsx b/web/source/settings/admin/actions/media/index.tsx index c904eb047..b3b805986 100644 --- a/web/source/settings/admin/actions/media/index.jsx +++ b/web/source/settings/admin/actions/media/index.tsx @@ -17,14 +17,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -const React = require("react"); -const Cleanup = require("./cleanup"); +import React from "react"; +import Cleanup from "./cleanup"; -module.exports = function Media() { +export default function Media() { return ( <> <h1>Media Actions</h1> <Cleanup /> </> ); -}; +} diff --git a/web/source/settings/admin/domain-permissions/form.tsx b/web/source/settings/admin/domain-permissions/form.tsx index fb639202d..57502d6d9 100644 --- a/web/source/settings/admin/domain-permissions/form.tsx +++ b/web/source/settings/admin/domain-permissions/form.tsx @@ -100,9 +100,9 @@ export default function ImportExportForm({ form, submitParse, parseResult }: Imp onClick={() => submitParse()} result={parseResult} showError={false} - disabled={false} + disabled={form.permType.value === undefined || form.permType.value.length === 0} /> - <label className="button with-icon"> + <label className={`button with-icon${form.permType.value === undefined || form.permType.value.length === 0 ? " disabled" : ""}`}> <i className="fa fa-fw " aria-hidden="true" /> Import file <input @@ -110,6 +110,7 @@ export default function ImportExportForm({ form, submitParse, parseResult }: Imp className="hidden" onChange={fileChanged} accept="application/json,text/plain,text/csv" + disabled={form.permType.value === undefined || form.permType.value.length === 0} /> </label> <b /> {/* grid filler */} @@ -118,7 +119,7 @@ export default function ImportExportForm({ form, submitParse, parseResult }: Imp type="button" onClick={() => submitExport("export")} result={exportResult} showError={false} - disabled={false} + disabled={form.permType.value === undefined || form.permType.value.length === 0} /> <MutationButton label="Export to file" @@ -127,7 +128,7 @@ export default function ImportExportForm({ form, submitParse, parseResult }: Imp onClick={() => submitExport("export-file")} result={exportResult} showError={false} - disabled={false} + disabled={form.permType.value === undefined || form.permType.value.length === 0} /> <div className="export-file"> <span> diff --git a/web/source/settings/admin/emoji/local/detail.js b/web/source/settings/admin/emoji/local/detail.js index 18a681b6e..a78e3e499 100644 --- a/web/source/settings/admin/emoji/local/detail.js +++ b/web/source/settings/admin/emoji/local/detail.js @@ -17,29 +17,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -const React = require("react"); -const { useRoute, Link, Redirect } = require("wouter"); +import React, { useEffect } from "react"; +import { useRoute, Link, Redirect } from "wouter"; -const { useComboBoxInput, useFileInput, useValue } = require("../../../lib/form"); -const { CategorySelect } = require("../category-select"); +import { useComboBoxInput, useFileInput, useValue } from "../../../lib/form"; +import { CategorySelect } from "../category-select"; -const useFormSubmit = require("../../../lib/form/submit").default; -const { useBaseUrl } = require("../../../lib/navigation/util"); +import useFormSubmit from "../../../lib/form/submit"; +import { useBaseUrl } from "../../../lib/navigation/util"; -const FakeToot = require("../../../components/fake-toot"); -const FormWithData = require("../../../lib/form/form-with-data").default; -const Loading = require("../../../components/loading"); -const { FileInput } = require("../../../components/form/inputs"); -const MutationButton = require("../../../components/form/mutation-button"); -const { Error } = require("../../../components/error"); +import FakeToot from "../../../components/fake-toot"; +import FormWithData from "../../../lib/form/form-with-data"; +import Loading from "../../../components/loading"; +import { FileInput } from "../../../components/form/inputs"; +import MutationButton from "../../../components/form/mutation-button"; +import { Error } from "../../../components/error"; -const { - useGetEmojiQuery, - useEditEmojiMutation, - useDeleteEmojiMutation, -} = require("../../../lib/query/admin/custom-emoji"); +import { useGetEmojiQuery, useEditEmojiMutation, useDeleteEmojiMutation } from "../../../lib/query/admin/custom-emoji"; -module.exports = function EmojiDetailRoute({ }) { +export default function EmojiDetailRoute({ }) { const baseUrl = useBaseUrl(); let [_match, params] = useRoute(`${baseUrl}/:emojiId`); if (params?.emojiId == undefined) { @@ -52,7 +48,7 @@ module.exports = function EmojiDetailRoute({ }) { </div> ); } -}; +} function EmojiDetailForm({ data: emoji }) { const baseUrl = useBaseUrl(); @@ -68,7 +64,7 @@ function EmojiDetailForm({ data: emoji }) { const [modifyEmoji, result] = useFormSubmit(form, useEditEmojiMutation()); // Automatic submitting of category change - React.useEffect(() => { + useEffect(() => { if ( form.category.hasChanged() && !form.category.state.open && diff --git a/web/source/settings/admin/emoji/local/index.js b/web/source/settings/admin/emoji/local/index.tsx index 008bd7a61..74a891f3e 100644 --- a/web/source/settings/admin/emoji/local/index.js +++ b/web/source/settings/admin/emoji/local/index.tsx @@ -17,13 +17,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -const React = require("react"); -const { Switch, Route } = require("wouter"); +import React from "react"; +import { Switch, Route } from "wouter"; -const EmojiOverview = require("./overview"); -const EmojiDetail = require("./detail"); +import EmojiOverview from "./overview"; +import EmojiDetail from "./detail"; -module.exports = function CustomEmoji({ baseUrl }) { +export default function CustomEmoji({ baseUrl }) { return ( <Switch> <Route path={`${baseUrl}/:emojiId`}> @@ -32,4 +32,4 @@ module.exports = function CustomEmoji({ baseUrl }) { <EmojiOverview /> </Switch> ); -}; +} diff --git a/web/source/settings/admin/emoji/local/new-emoji.js b/web/source/settings/admin/emoji/local/new-emoji.tsx index 6c0d0f2f4..c6a203765 100644 --- a/web/source/settings/admin/emoji/local/new-emoji.js +++ b/web/source/settings/admin/emoji/local/new-emoji.tsx @@ -17,31 +17,26 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -const React = require("react"); +import React, { useMemo, useEffect } from "react"; -const { - useFileInput, - useComboBoxInput -} = require("../../../lib/form"); -const useShortcode = require("./use-shortcode"); +import { useFileInput, useComboBoxInput } from "../../../lib/form"; +import useShortcode from "./use-shortcode"; -const useFormSubmit = require("../../../lib/form/submit").default; +import useFormSubmit from "../../../lib/form/submit"; -const { - TextInput, FileInput -} = require("../../../components/form/inputs"); +import { TextInput, FileInput } from "../../../components/form/inputs"; -const { CategorySelect } = require('../category-select'); -const FakeToot = require("../../../components/fake-toot"); -const MutationButton = require("../../../components/form/mutation-button"); -const { useAddEmojiMutation } = require("../../../lib/query/admin/custom-emoji"); -const { useInstanceV1Query } = require("../../../lib/query"); +import { CategorySelect } from '../category-select'; +import FakeToot from "../../../components/fake-toot"; +import MutationButton from "../../../components/form/mutation-button"; +import { useAddEmojiMutation } from "../../../lib/query/admin/custom-emoji"; +import { useInstanceV1Query } from "../../../lib/query"; -module.exports = function NewEmojiForm() { +export default function NewEmojiForm() { const shortcode = useShortcode(); const { data: instance } = useInstanceV1Query(); - const emojiMaxSize = React.useMemo(() => { + const emojiMaxSize = useMemo(() => { return instance?.configuration?.emojis?.emoji_size_limit ?? 50 * 1024; }, [instance]); @@ -56,8 +51,8 @@ module.exports = function NewEmojiForm() { shortcode, image, category }, useAddEmojiMutation()); - React.useEffect(() => { - if (shortcode.value.length == 0) { + useEffect(() => { + if (shortcode.value === undefined || shortcode.value.length == 0) { if (image.value != undefined) { let [name, _ext] = image.value.name.split("."); shortcode.setter(name); @@ -71,7 +66,7 @@ module.exports = function NewEmojiForm() { /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [image.value]); - let emojiOrShortcode = `:${shortcode.value}:`; + let emojiOrShortcode; if (image.previewValue != undefined) { emojiOrShortcode = <img @@ -80,6 +75,10 @@ module.exports = function NewEmojiForm() { title={`:${shortcode.value}:`} alt={shortcode.value} />; + } else if (shortcode.value !== undefined && shortcode.value.length > 0) { + emojiOrShortcode = `:${shortcode.value}:`; + } else { + emojiOrShortcode = `:your_emoji_here:`; } return ( @@ -103,10 +102,15 @@ module.exports = function NewEmojiForm() { <CategorySelect field={category} + children={[]} /> - <MutationButton label="Upload emoji" result={result} /> + <MutationButton + disabled={image.previewValue === undefined} + label="Upload emoji" + result={result} + /> </form> </div> ); -};
\ No newline at end of file +}
\ No newline at end of file diff --git a/web/source/settings/admin/emoji/local/overview.js b/web/source/settings/admin/emoji/local/overview.js index 44b11f584..45bfd614d 100644 --- a/web/source/settings/admin/emoji/local/overview.js +++ b/web/source/settings/admin/emoji/local/overview.js @@ -22,7 +22,7 @@ const { Link } = require("wouter"); const syncpipe = require("syncpipe"); const { matchSorter } = require("match-sorter"); -const NewEmojiForm = require("./new-emoji"); +const NewEmojiForm = require("./new-emoji").default; const { useTextInput } = require("../../../lib/form"); const { useEmojiByCategory } = require("../category-select"); diff --git a/web/source/settings/admin/emoji/remote/index.js b/web/source/settings/admin/emoji/remote/index.tsx index 1a8c719dd..d9c786be2 100644 --- a/web/source/settings/admin/emoji/remote/index.js +++ b/web/source/settings/admin/emoji/remote/index.tsx @@ -17,15 +17,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -const React = require("react"); +import React, { useMemo } from "react"; -const ParseFromToot = require("./parse-from-toot"); +import ParseFromToot from "./parse-from-toot"; -const Loading = require("../../../components/loading"); -const { Error } = require("../../../components/error"); -const { useListEmojiQuery } = require("../../../lib/query/admin/custom-emoji"); +import Loading from "../../../components/loading"; +import { Error } from "../../../components/error"; +import { useListEmojiQuery } from "../../../lib/query/admin/custom-emoji"; -module.exports = function RemoteEmoji() { +export default function RemoteEmoji() { // local emoji are queried for shortcode collision detection const { data: emoji = [], @@ -33,7 +33,7 @@ module.exports = function RemoteEmoji() { error } = useListEmojiQuery({ filter: "domain:local" }); - const emojiCodes = React.useMemo(() => { + const emojiCodes = useMemo(() => { return new Set(emoji.map((e) => e.shortcode)); }, [emoji]); @@ -46,9 +46,9 @@ module.exports = function RemoteEmoji() { {isLoading ? <Loading /> : <> - <ParseFromToot emoji={emoji} emojiCodes={emojiCodes} /> + <ParseFromToot emojiCodes={emojiCodes} /> </> } </> ); -};
\ No newline at end of file +} diff --git a/web/source/settings/admin/emoji/remote/parse-from-toot.js b/web/source/settings/admin/emoji/remote/parse-from-toot.tsx index 7c29cccfd..df1c221ba 100644 --- a/web/source/settings/admin/emoji/remote/parse-from-toot.js +++ b/web/source/settings/admin/emoji/remote/parse-from-toot.tsx @@ -17,36 +17,28 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -const React = require("react"); +import React, { useCallback, useEffect } from "react"; -const { - useTextInput, - useComboBoxInput, - useCheckListInput -} = require("../../../lib/form"); +import { useTextInput, useComboBoxInput, useCheckListInput } from "../../../lib/form"; -const useFormSubmit = require("../../../lib/form/submit").default; +import useFormSubmit from "../../../lib/form/submit"; -const CheckList = require("../../../components/check-list").default; -const { CategorySelect } = require('../category-select'); +import CheckList from "../../../components/check-list"; +import { CategorySelect } from '../category-select'; -const { TextInput } = require("../../../components/form/inputs"); -const MutationButton = require("../../../components/form/mutation-button"); -const { Error } = require("../../../components/error"); -const { - useSearchItemForEmojiMutation, - usePatchRemoteEmojisMutation -} = require("../../../lib/query/admin/custom-emoji"); +import { TextInput } from "../../../components/form/inputs"; +import MutationButton from "../../../components/form/mutation-button"; +import { Error } from "../../../components/error"; +import { useSearchItemForEmojiMutation, usePatchRemoteEmojisMutation } from "../../../lib/query/admin/custom-emoji"; -module.exports = function ParseFromToot({ emojiCodes }) { +export default function ParseFromToot({ emojiCodes }) { const [searchStatus, result] = useSearchItemForEmojiMutation(); - - const [onURLChange, _resetURL, { url }] = useTextInput("url"); + const urlField = useTextInput("url"); function submitSearch(e) { e.preventDefault(); - if (url.trim().length != 0) { - searchStatus(url); + if (urlField.value !== undefined && urlField.value.trim().length != 0) { + searchStatus(urlField.value); } } @@ -63,8 +55,8 @@ module.exports = function ParseFromToot({ emojiCodes }) { type="text" id="url" name="url" - onChange={onURLChange} - value={url} + onChange={urlField.onChange} + value={urlField.value} /> <button disabled={result.isLoading}> <i className={[ @@ -81,7 +73,7 @@ module.exports = function ParseFromToot({ emojiCodes }) { <SearchResult result={result} localEmojiCodes={emojiCodes} /> </div> ); -}; +} function SearchResult({ result, localEmojiCodes }) { const { error, data, isSuccess, isError } = result; @@ -106,7 +98,6 @@ function SearchResult({ result, localEmojiCodes }) { <CopyEmojiForm localEmojiCodes={localEmojiCodes} type={data.type} - domain={data.domain} emojiList={data.list} /> ); @@ -139,13 +130,16 @@ function CopyEmojiForm({ localEmojiCodes, type, emojiList }) { ); const buttonsInactive = form.selectedEmoji.someSelected - ? {} + ? { + disabled: false, + title: "" + } : { disabled: true, title: "No emoji selected, cannot perform any actions" }; - const checkListExtraProps = React.useCallback(() => ({ localEmojiCodes }), [localEmojiCodes]); + const checkListExtraProps = useCallback(() => ({ localEmojiCodes }), [localEmojiCodes]); return ( <div className="parsed"> @@ -153,17 +147,32 @@ function CopyEmojiForm({ localEmojiCodes, type, emojiList }) { <form onSubmit={formSubmit}> <CheckList field={form.selectedEmoji} + header={<></>} EntryComponent={EmojiEntry} getExtraProps={checkListExtraProps} /> <CategorySelect field={form.category} + children={[]} /> <div className="action-buttons row"> - <MutationButton name="copy" label="Copy to local emoji" result={result} showError={false} {...buttonsInactive} /> - <MutationButton name="disable" label="Disable" result={result} className="button danger" showError={false} {...buttonsInactive} /> + <MutationButton + name="copy" + label="Copy to local emoji" + result={result} + showError={false} + {...buttonsInactive} + /> + <MutationButton + name="disable" + label="Disable" + result={result} + className="button danger" + showError={false} + {...buttonsInactive} + /> </div> {result.error && ( Array.isArray(result.error) @@ -198,13 +207,13 @@ function EmojiEntry({ entry: emoji, onChange, extraProps: { localEmojiCodes } }) } }); - React.useEffect(() => { + useEffect(() => { if (emoji.valid != shortcodeField.valid) { onChange({ valid: shortcodeField.valid }); } }, [onChange, emoji.valid, shortcodeField.valid]); - React.useEffect(() => { + useEffect(() => { shortcodeField.validate(); // only need this update if it's the emoji.checked that updated, not shortcodeField // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/web/source/settings/admin/reports/detail.jsx b/web/source/settings/admin/reports/detail.tsx index 70d576080..94268dc1f 100644 --- a/web/source/settings/admin/reports/detail.jsx +++ b/web/source/settings/admin/reports/detail.tsx @@ -17,26 +17,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -const React = require("react"); -const { useRoute, Redirect } = require("wouter"); +import React, { useState } from "react"; +import { useRoute, Redirect } from "wouter"; -const FormWithData = require("../../lib/form/form-with-data").default; -const BackButton = require("../../components/back-button"); +import FormWithData from "../../lib/form/form-with-data"; +import BackButton from "../../components/back-button"; -const { useValue, useTextInput } = require("../../lib/form"); -const useFormSubmit = require("../../lib/form/submit").default; +import { useValue, useTextInput } from "../../lib/form"; +import useFormSubmit from "../../lib/form/submit"; -const { TextArea } = require("../../components/form/inputs"); +import { TextArea } from "../../components/form/inputs"; -const MutationButton = require("../../components/form/mutation-button"); -const Username = require("./username"); -const { useBaseUrl } = require("../../lib/navigation/util"); -const { - useGetReportQuery, - useResolveReportMutation, -} = require("../../lib/query/admin/reports"); +import MutationButton from "../../components/form/mutation-button"; +import Username from "./username"; +import { useBaseUrl } from "../../lib/navigation/util"; +import { useGetReportQuery, useResolveReportMutation } from "../../lib/query/admin/reports"; -module.exports = function ReportDetail({ }) { +export default function ReportDetail({ }) { const baseUrl = useBaseUrl(); let [_match, params] = useRoute(`${baseUrl}/:reportId`); if (params?.reportId == undefined) { @@ -55,7 +52,7 @@ module.exports = function ReportDetail({ }) { </div> ); } -}; +} function ReportDetailForm({ data: report }) { const from = report.account; @@ -131,7 +128,11 @@ function ReportActionForm({ report }) { field={form.comment} label="Comment" /> - <MutationButton label="Resolve" result={result} /> + <MutationButton + disabled={false} + label="Resolve" + result={result} + /> </form> ); } @@ -170,10 +171,10 @@ function ReportedToot({ toot }) { } </section> <aside className="status-info"> - <dl class="status-stats"> - <div class="stats-grouping"> - <div class="stats-item published-at text-cutoff"> - <dt class="sr-only">Published</dt> + <dl className="status-stats"> + <div className="stats-grouping"> + <div className="stats-item published-at text-cutoff"> + <dt className="sr-only">Published</dt> <dd> <time dateTime={toot.created_at}>{new Date(toot.created_at).toLocaleString()}</time> </dd> @@ -186,7 +187,7 @@ function ReportedToot({ toot }) { } function TootCW({ note, content }) { - const [visible, setVisible] = React.useState(false); + const [visible, setVisible] = useState(false); function toggleVisible() { setVisible(!visible); @@ -217,12 +218,12 @@ function TootMedia({ media, sensitive }) { <input id={`sensitiveMedia-${m.id}`} type="checkbox" className="sensitive-checkbox hidden" /> <div className="sensitive"> <div className="open"> - <label htmlFor={`sensitiveMedia-${m.id}`} className="button" role="button" tabIndex="0"> + <label htmlFor={`sensitiveMedia-${m.id}`} className="button" role="button" tabIndex={0}> <i className="fa fa-eye-slash" title="Hide sensitive media"></i> </label> </div> <div className="closed" title={m.description}> - <label htmlFor={`sensitiveMedia-${m.id}`} className="button" role="button" tabIndex="0"> + <label htmlFor={`sensitiveMedia-${m.id}`} className="button" role="button" tabIndex={0}> Show sensitive media </label> </div> @@ -241,12 +242,11 @@ function TootMedia({ media, sensitive }) { alt={m.description} src={m.url} // thumb={m.preview_url} - size={m.meta?.original} - type={m.type} + sizes={m.meta?.original} /> </a> </div> ))} </div> ); -}
\ No newline at end of file +} diff --git a/web/source/settings/admin/reports/index.jsx b/web/source/settings/admin/reports/index.tsx index 58fca998d..052d72761 100644 --- a/web/source/settings/admin/reports/index.jsx +++ b/web/source/settings/admin/reports/index.tsx @@ -17,17 +17,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -const React = require("react"); -const { Link, Switch, Route } = require("wouter"); +import React from "react"; +import { Link, Switch, Route } from "wouter"; -const FormWithData = require("../../lib/form/form-with-data").default; +import FormWithData from "../../lib/form/form-with-data"; -const ReportDetail = require("./detail"); -const Username = require("./username"); -const { useBaseUrl } = require("../../lib/navigation/util"); -const { useListReportsQuery } = require("../../lib/query/admin/reports"); +import ReportDetail from "./detail"; +import Username from "./username"; +import { useBaseUrl } from "../../lib/navigation/util"; +import { useListReportsQuery } from "../../lib/query/admin/reports"; -module.exports = function Reports({ baseUrl }) { +export default function Reports({ baseUrl }) { return ( <div className="reports"> <Switch> @@ -38,7 +38,7 @@ module.exports = function Reports({ baseUrl }) { </Switch> </div> ); -}; +} function ReportOverview({ }) { return ( @@ -100,4 +100,4 @@ function ReportEntry({ report }) { </a> </Link> ); -}
\ No newline at end of file +} diff --git a/web/source/settings/admin/reports/username.jsx b/web/source/settings/admin/reports/username.tsx index 9754c2dd5..6fba0b804 100644 --- a/web/source/settings/admin/reports/username.jsx +++ b/web/source/settings/admin/reports/username.tsx @@ -17,10 +17,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -const React = require("react"); -const { Link } = require("wouter"); +import React from "react"; +import { Link } from "wouter"; -module.exports = function Username({ user, link = true }) { +export default function Username({ user, link = true }) { let className = "user"; let isLocal = user.domain == null; @@ -36,8 +36,8 @@ module.exports = function Username({ user, link = true }) { ? { fa: "fa-home", info: "Local user" } : { fa: "fa-external-link-square", info: "Remote user" }; - let Element = "div"; - let href = null; + let Element: any = "div"; + let href: any = null; if (link) { Element = Link; @@ -51,4 +51,4 @@ module.exports = function Username({ user, link = true }) { <span className="sr-only">{icon.info}</span> </Element> ); -};
\ No newline at end of file +} diff --git a/web/source/settings/admin/settings/rules.jsx b/web/source/settings/admin/settings/rules.tsx index 4280ccea7..e5e4d17c5 100644 --- a/web/source/settings/admin/settings/rules.jsx +++ b/web/source/settings/admin/settings/rules.tsx @@ -17,28 +17,29 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -const React = require("react"); -const { Switch, Route, Link, Redirect, useRoute } = require("wouter"); +import React from "react"; +import { Switch, Route, Link, Redirect, useRoute } from "wouter"; -const query = require("../../lib/query"); -const FormWithData = require("../../lib/form/form-with-data").default; -const { useBaseUrl } = require("../../lib/navigation/util"); +import { useInstanceRulesQuery, useAddInstanceRuleMutation, useUpdateInstanceRuleMutation, useDeleteInstanceRuleMutation } from "../../lib/query"; +import FormWithData from "../../lib/form/form-with-data"; +import { useBaseUrl } from "../../lib/navigation/util"; -const { useValue, useTextInput } = require("../../lib/form"); -const useFormSubmit = require("../../lib/form/submit").default; +import { useValue, useTextInput } from "../../lib/form"; +import useFormSubmit from "../../lib/form/submit"; -const { TextArea } = require("../../components/form/inputs"); -const MutationButton = require("../../components/form/mutation-button"); +import { TextArea } from "../../components/form/inputs"; +import MutationButton from "../../components/form/mutation-button"; +import { Error } from "../../components/error"; -module.exports = function InstanceRulesData({ baseUrl }) { +export default function InstanceRulesData({ baseUrl }) { return ( <FormWithData - dataQuery={query.useInstanceRulesQuery} + dataQuery={useInstanceRulesQuery} DataForm={InstanceRules} - baseUrl={baseUrl} + {...{baseUrl}} /> ); -}; +} function InstanceRules({ baseUrl, data: rules }) { return ( @@ -64,7 +65,8 @@ function InstanceRules({ baseUrl, data: rules }) { function InstanceRuleList({ rules }) { const newRule = useTextInput("text", {}); - const [submitForm, result] = useFormSubmit({ newRule }, query.useAddInstanceRuleMutation(), { + const [submitForm, result] = useFormSubmit({ newRule }, useAddInstanceRuleMutation(), { + changedOnly: true, onFinish: () => newRule.reset() }); @@ -72,7 +74,7 @@ function InstanceRuleList({ rules }) { <> <form onSubmit={submitForm} className="new-rule"> <ol className="instance-rules"> - {Object.values(rules).map((rule) => ( + {Object.values(rules).map((rule: any) => ( <InstanceRule key={rule.id} rule={rule} /> ))} </ol> @@ -80,7 +82,11 @@ function InstanceRuleList({ rules }) { field={newRule} label="New instance rule" /> - <MutationButton label="Add rule" result={result} /> + <MutationButton + disabled={newRule.value === undefined || newRule.value.length === 0} + label="Add rule" + result={result} + /> </form> </> ); @@ -124,9 +130,9 @@ function InstanceRuleForm({ rule }) { rule: useTextInput("text", { defaultValue: rule.text }) }; - const [submitForm, result] = useFormSubmit(form, query.useUpdateInstanceRuleMutation()); + const [submitForm, result] = useFormSubmit(form, useUpdateInstanceRuleMutation()); - const [deleteRule, deleteResult] = query.useDeleteInstanceRuleMutation({ fixedCacheKey: rule.id }); + const [deleteRule, deleteResult] = useDeleteInstanceRuleMutation({ fixedCacheKey: rule.id }); if (result.isSuccess || deleteResult.isSuccess) { return ( @@ -150,6 +156,7 @@ function InstanceRuleForm({ rule }) { /> <MutationButton + disabled={false} type="button" onClick={() => deleteRule(rule.id)} label="Delete" @@ -164,4 +171,4 @@ function InstanceRuleForm({ rule }) { </form> </div> ); -}
\ No newline at end of file +} |