mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-03 00:27:30 -05:00
Autoban on repeated FC + admin FC search Closes #2024
This commit is contained in:
6
app/features/admin/admin-schemas.ts
Normal file
6
app/features/admin/admin-schemas.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import { friendCode } from "~/utils/zod";
|
||||
|
||||
export const adminActionSearchParamsSchema = z.object({
|
||||
friendCode,
|
||||
});
|
||||
@@ -1,10 +1,17 @@
|
||||
import type { LoaderFunction } from "@remix-run/node";
|
||||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { redirect } from "@remix-run/node";
|
||||
import { getUserId, isImpersonating } from "~/features/auth/core/user.server";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
import { isMod } from "~/permissions";
|
||||
import { parseSafeSearchParams } from "~/utils/remix.server";
|
||||
import { adminActionSearchParamsSchema } from "../admin-schemas";
|
||||
|
||||
export const loader: LoaderFunction = async ({ request }) => {
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const user = await getUserId(request);
|
||||
const parsedSearchParams = parseSafeSearchParams({
|
||||
request,
|
||||
schema: adminActionSearchParamsSchema,
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === "production" && !isMod(user)) {
|
||||
throw redirect("/");
|
||||
@@ -12,5 +19,10 @@ export const loader: LoaderFunction = async ({ request }) => {
|
||||
|
||||
return {
|
||||
isImpersonating: await isImpersonating(request),
|
||||
friendCodeSearchUsers: parsedSearchParams.success
|
||||
? await UserRepository.findByFriendCode(
|
||||
parsedSearchParams.data.friendCode,
|
||||
)
|
||||
: [],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
import type { MetaFunction } from "@remix-run/node";
|
||||
import {
|
||||
Form,
|
||||
Link,
|
||||
useFetcher,
|
||||
useLoaderData,
|
||||
useNavigation,
|
||||
useSearchParams,
|
||||
} from "@remix-run/react";
|
||||
import * as React from "react";
|
||||
import { Avatar } from "~/components/Avatar";
|
||||
import { Button } from "~/components/Button";
|
||||
import { Catcher } from "~/components/Catcher";
|
||||
import { Input } from "~/components/Input";
|
||||
import { Main } from "~/components/Main";
|
||||
import { NewTabs } from "~/components/NewTabs";
|
||||
import { SubmitButton } from "~/components/SubmitButton";
|
||||
import { UserSearch } from "~/components/UserSearch";
|
||||
import { SearchIcon } from "~/components/icons/Search";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import { FRIEND_CODE_REGEXP_PATTERN } from "~/features/sendouq/q-constants";
|
||||
import { isAdmin, isMod } from "~/permissions";
|
||||
import { metaTags } from "~/utils/remix";
|
||||
import { SEED_URL, STOP_IMPERSONATING_URL, impersonateUrl } from "~/utils/urls";
|
||||
import {
|
||||
SEED_URL,
|
||||
STOP_IMPERSONATING_URL,
|
||||
impersonateUrl,
|
||||
userPage,
|
||||
} from "~/utils/urls";
|
||||
|
||||
import { action } from "../actions/admin.server";
|
||||
import { loader } from "../loaders/admin.server";
|
||||
@@ -30,10 +40,78 @@ export const meta: MetaFunction = (args) => {
|
||||
};
|
||||
|
||||
export default function AdminPage() {
|
||||
return (
|
||||
<Main>
|
||||
<NewTabs
|
||||
tabs={[
|
||||
{
|
||||
label: "Actions",
|
||||
},
|
||||
{
|
||||
label: "Friend code look-up",
|
||||
},
|
||||
]}
|
||||
content={[
|
||||
{
|
||||
key: "actions",
|
||||
element: <AdminActions />,
|
||||
},
|
||||
{
|
||||
key: "friend-code-look-up",
|
||||
element: <FriendCodeLookUp />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Main>
|
||||
);
|
||||
}
|
||||
|
||||
function FriendCodeLookUp() {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [friendCode, setFriendCode] = React.useState(
|
||||
searchParams.get("friendCode") ?? "",
|
||||
);
|
||||
const fetcher = useFetcher();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="stack md horizontal justify-center">
|
||||
<Input
|
||||
placeholder="1234-5678-9101"
|
||||
name="friendCode"
|
||||
value={friendCode}
|
||||
onChange={(e) => setFriendCode(e.target.value)}
|
||||
/>
|
||||
<SubmitButton
|
||||
state={fetcher.state}
|
||||
icon={<SearchIcon />}
|
||||
onClick={() => setSearchParams({ friendCode })}
|
||||
>
|
||||
Search
|
||||
</SubmitButton>
|
||||
</div>
|
||||
<div className="stack lg">
|
||||
{data.friendCodeSearchUsers?.map((user) => (
|
||||
<Link
|
||||
key={user.id}
|
||||
to={userPage(user)}
|
||||
className="stack horizontal sm text-main-forced items-center"
|
||||
>
|
||||
<Avatar user={user} size="sm" />
|
||||
{user.username}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AdminActions() {
|
||||
const user = useUser();
|
||||
|
||||
return (
|
||||
<Main className="stack lg">
|
||||
<div className="stack lg">
|
||||
{process.env.NODE_ENV !== "production" && <Seed />}
|
||||
|
||||
{isMod(user) ? <LinkPlayer /> : null}
|
||||
@@ -51,7 +129,7 @@ export default function AdminPage() {
|
||||
{isMod(user) ? <UnbanUser /> : null}
|
||||
{isAdmin(user) ? <RefreshPlusTiers /> : null}
|
||||
{isAdmin(user) ? <CleanUp /> : null}
|
||||
</Main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user