diff --git a/app/routes/report.tsx b/app/routes/report.tsx
index 275c435..828bbd4 100644
--- a/app/routes/report.tsx
+++ b/app/routes/report.tsx
@@ -10,6 +10,7 @@ import {
Input,
Link,
Text,
+ Textarea,
useToast,
} from "@chakra-ui/react";
import { useEffect, useState } from "react";
@@ -96,9 +97,8 @@ export default function () {
).value
.replaceAll(" ", "")
.split(",");
- const file = (
- document.getElementById("evidence") as HTMLInputElement
- ).files?.item(0);
+ const files = (document.getElementById("evidence") as HTMLInputElement)
+ .files;
if (!usernames.length)
return toast({
@@ -108,9 +108,9 @@ export default function () {
title: "Error",
});
- if (!file)
+ if (!files?.length)
return toast({
- description: "Must attach a file",
+ description: "Must attach at least one file",
isClosable: true,
status: "error",
title: "Error",
@@ -124,10 +124,29 @@ export default function () {
title: "Too Many Usernames",
});
+ if (!logged_in && !turnstileToken)
+ return toast({
+ description: "Please complete the captcha and try again",
+ isClosable: true,
+ status: "error",
+ title: "Captcha not completed",
+ });
+
+ const description = (
+ document.getElementById("description") as HTMLTextAreaElement
+ ).value;
+
+ const filelist = [];
+
+ for (const file of files) {
+ filelist.push({ name: file.name, size: file.size });
+ }
+
const submitReq = await fetch("/api/reports/submit", {
body: JSON.stringify({
- filename: file.name,
- filesize: file.size,
+ description: description || undefined,
+ files: filelist,
+ turnstileResponse: logged_in ? undefined : turnstileToken,
usernames,
}),
headers: {
@@ -152,42 +171,62 @@ export default function () {
});
}
- const { id, upload_url }: { id: string; upload_url: string } =
+ const { id, upload_urls }: { id: string; upload_urls: string[] } =
await submitReq.json();
- setUploading(true);
- const reader = file.stream().getReader();
+ const totalSize = filelist.reduce((a, b) => a + b.size, 0);
let bytesRead = 0;
+ let shouldRecall = false;
- 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 ||
- fileTypes[file.name.split(".")[file.name.split(".").length - 1]],
- },
- method: "PUT",
- }).catch(console.error);
+ setUploading(true);
+
+ for (let i = 0; i < upload_urls.length; i++) {
+ const reader = files[i].stream().getReader();
- if (!uploadReq?.ok) {
+ try {
+ const uploadReq = await fetch(upload_urls[i], {
+ body: supportsRequestStreams
+ ? new ReadableStream({
+ async pull(controller) {
+ const chunk = await reader.read();
+
+ if (chunk.done) {
+ controller.close();
+
+ if (i === upload_urls.length - 1) setUploading(false);
+
+ return;
+ }
+
+ controller.enqueue(chunk.value);
+ bytesRead += chunk.value.length;
+ setFileProgress(Math.floor((bytesRead / totalSize) * 100));
+ },
+ })
+ : files[i],
+ // @ts-expect-error
+ duplex: supportsRequestStreams ? "half" : undefined,
+ headers: {
+ "content-type":
+ files[i].type ||
+ fileTypes[files[i].name.split(".").at(-1) as string],
+ },
+ method: "PUT",
+ });
+
+ if (!uploadReq.ok) {
+ shouldRecall = true;
+ break;
+ }
+ } catch (e) {
+ console.error(e);
+
+ shouldRecall = true;
+ break;
+ }
+ }
+
+ if (shouldRecall) {
await fetch("/api/reports/recall", {
body: JSON.stringify({ id }),
headers: {
@@ -245,7 +284,7 @@ export default function () {
+