Remix migration
This commit is contained in:
254
app/routes/report.tsx
Normal file
254
app/routes/report.tsx
Normal file
@ -0,0 +1,254 @@
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
CircularProgressLabel,
|
||||
Container,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Heading,
|
||||
HStack,
|
||||
Input,
|
||||
Link,
|
||||
Text,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useLoaderData } from "@remix-run/react";
|
||||
import Success from "../../components/Success.js";
|
||||
|
||||
export async function loader({
|
||||
context,
|
||||
}: {
|
||||
context: RequestContext;
|
||||
}): Promise<{ logged_in: boolean; site_key: string }> {
|
||||
return {
|
||||
logged_in: Boolean(context.data.current_user),
|
||||
site_key: context.env.TURNSTILE_SITEKEY,
|
||||
};
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const [fileProgress, setFileProgress] = useState(0);
|
||||
const [showSuccess, setShowSuccess] = useState(false);
|
||||
const [supportsRequestStreams, setSupportsRequestStreams] = useState(false);
|
||||
const toast = useToast();
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (sessionStorage.getItem("REPORT_SUCCESS")) {
|
||||
sessionStorage.removeItem("REPORT_SUCCESS");
|
||||
return setShowSuccess(true);
|
||||
}
|
||||
|
||||
setSupportsRequestStreams(
|
||||
(() => {
|
||||
let duplexAccessed = false;
|
||||
|
||||
const hasContentType = new Request("", {
|
||||
body: new ReadableStream(),
|
||||
method: "POST",
|
||||
// @ts-ignore
|
||||
get duplex() {
|
||||
duplexAccessed = true;
|
||||
return "half";
|
||||
},
|
||||
}).headers.has("Content-Type");
|
||||
|
||||
return duplexAccessed && !hasContentType;
|
||||
})()
|
||||
);
|
||||
}, []);
|
||||
|
||||
async function submit() {
|
||||
const usernames = (
|
||||
document.getElementById("usernames") as HTMLInputElement
|
||||
).value
|
||||
.replaceAll(" ", "")
|
||||
.split(",");
|
||||
const file = (
|
||||
document.getElementById("evidence") as HTMLInputElement
|
||||
).files?.item(0);
|
||||
|
||||
if (!usernames.length)
|
||||
return toast({
|
||||
description: "Must provide at least one username",
|
||||
isClosable: true,
|
||||
status: "error",
|
||||
title: "Error",
|
||||
});
|
||||
|
||||
if (!file)
|
||||
return toast({
|
||||
description: "Must attach a file",
|
||||
isClosable: true,
|
||||
status: "error",
|
||||
title: "Error",
|
||||
});
|
||||
|
||||
if (usernames.length > 20)
|
||||
return toast({
|
||||
description: "Only up to twenty users can be reported at a time",
|
||||
isClosable: true,
|
||||
status: "error",
|
||||
title: "Too Many Usernames",
|
||||
});
|
||||
|
||||
const submitReq = await fetch("/api/reports/submit", {
|
||||
body: JSON.stringify({
|
||||
filename: file.name,
|
||||
filesize: file.size,
|
||||
usernames,
|
||||
}),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
if (!submitReq.ok)
|
||||
return toast({
|
||||
description: ((await submitReq.json()) as { error: string }).error,
|
||||
isClosable: true,
|
||||
status: "error",
|
||||
title: "Error",
|
||||
});
|
||||
|
||||
const { id, upload_url }: { id: string; upload_url: string } =
|
||||
await submitReq.json();
|
||||
|
||||
setUploading(true);
|
||||
const reader = file.stream().getReader();
|
||||
let bytesRead = 0;
|
||||
|
||||
const uploadReq = await fetch(upload_url, {
|
||||
body: supportsRequestStreams
|
||||
? new ReadableStream({
|
||||
async pull(controller) {
|
||||
const chunk = await reader.read();
|
||||
|
||||
if (chunk.done) {
|
||||
controller.close();
|
||||
setUploading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
controller.enqueue(chunk.value);
|
||||
bytesRead += chunk.value.length;
|
||||
setFileProgress(Math.floor((bytesRead / file.size) * 100));
|
||||
},
|
||||
})
|
||||
: file,
|
||||
// @ts-expect-error
|
||||
duplex: supportsRequestStreams ? "half" : undefined,
|
||||
headers: {
|
||||
"content-type": file.type,
|
||||
},
|
||||
method: "PUT",
|
||||
}).catch(console.error);
|
||||
|
||||
if (!uploadReq?.ok) {
|
||||
await fetch("/api/reports/recall", {
|
||||
body: JSON.stringify({ id }),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
return toast({
|
||||
description: "Failed to upload file",
|
||||
isClosable: true,
|
||||
status: "error",
|
||||
title: "Error",
|
||||
});
|
||||
}
|
||||
|
||||
await fetch("/api/reports/complete", {
|
||||
body: JSON.stringify({ id }),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
sessionStorage.setItem("REPORT_SUCCESS", "1");
|
||||
}
|
||||
|
||||
const { logged_in, site_key } = useLoaderData<typeof loader>();
|
||||
|
||||
return showSuccess ? (
|
||||
<Success
|
||||
heading="Report Submitted"
|
||||
message="We will review it as soon as possible."
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Container maxW="container.md" pt="4vh" textAlign="start">
|
||||
<Heading mb="4vh">Report an Exploiter</Heading>
|
||||
<br />
|
||||
<FormControl isRequired>
|
||||
<FormLabel>
|
||||
Username(s) - To specify more than one, provide a comma-delimited
|
||||
list (User1, User2, User3...)
|
||||
</FormLabel>
|
||||
<Input id="usernames" placeholder="builderman" />
|
||||
</FormControl>
|
||||
<br />
|
||||
<FormControl isRequired>
|
||||
<FormLabel>Your Evidence (Max Size: 512MB)</FormLabel>
|
||||
<Button
|
||||
colorScheme="blue"
|
||||
mr="8px"
|
||||
onClick={() => document.getElementById("evidence")?.click()}
|
||||
>
|
||||
Select File
|
||||
</Button>
|
||||
<input id="evidence" type="file" />
|
||||
</FormControl>
|
||||
<br />
|
||||
<br />
|
||||
<Text>
|
||||
By submitting this form, you agree to the{" "}
|
||||
<Link color="#646cff" href="/terms">
|
||||
Terms of Service
|
||||
</Link>{" "}
|
||||
and{" "}
|
||||
<Link color="#646cff" href="/privacy">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
.
|
||||
</Text>
|
||||
<br />
|
||||
<HStack>
|
||||
<Button
|
||||
disabled={uploading}
|
||||
mr="8px"
|
||||
onClick={async () => await submit()}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<CircularProgress
|
||||
display={uploading ? "" : "none"}
|
||||
isIndeterminate={!supportsRequestStreams}
|
||||
value={supportsRequestStreams ? fileProgress : undefined}
|
||||
>
|
||||
{supportsRequestStreams ? (
|
||||
<CircularProgressLabel>{fileProgress}%</CircularProgressLabel>
|
||||
) : null}
|
||||
</CircularProgress>
|
||||
</HStack>
|
||||
<div
|
||||
className="cf-turnstile"
|
||||
data-sitekey={useLoaderData<typeof loader>()}
|
||||
></div>
|
||||
</Container>
|
||||
{logged_in ? null : (
|
||||
<script
|
||||
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
|
||||
async
|
||||
defer
|
||||
></script>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user