Initial commit
This commit is contained in:
43
pages/appeals.page.server.tsx
Normal file
43
pages/appeals.page.server.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
export async function onBeforeRender(pageContext: PageContext) {
|
||||
if (!pageContext.current_user)
|
||||
return {
|
||||
pageContext: {
|
||||
pageProps: {
|
||||
logged_in: false,
|
||||
},
|
||||
status: 401,
|
||||
},
|
||||
};
|
||||
|
||||
const blockedAppeal = await pageContext.kv?.get(
|
||||
`blockedappeal_${pageContext.current_user.id}`
|
||||
);
|
||||
const disabledStatus = await pageContext.kv?.get("appeal_disabled");
|
||||
const openAppeals = await pageContext.kv?.list({
|
||||
prefix: `appeal_${pageContext.current_user.id}`,
|
||||
});
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
pageProps: {
|
||||
can_appeal:
|
||||
!Boolean(disabledStatus) &&
|
||||
!Boolean(blockedAppeal) &&
|
||||
!Boolean(
|
||||
openAppeals.keys.find(
|
||||
(appeal) => (appeal.metadata as { [k: string]: any }).open
|
||||
)
|
||||
),
|
||||
can_toggle:
|
||||
pageContext.current_user?.permissions & (1 << 0) ||
|
||||
pageContext.current_user?.permissions & (1 << 11),
|
||||
disabled: Boolean(disabledStatus),
|
||||
logged_in: true,
|
||||
},
|
||||
status: pageContext.current_user ? 200 : 401,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const description = "Appeal your Discord ban here.";
|
||||
export const title = "Appeals - Car Crushers";
|
||||
205
pages/appeals.page.tsx
Normal file
205
pages/appeals.page.tsx
Normal file
@@ -0,0 +1,205 @@
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertIcon,
|
||||
AlertTitle,
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Flex,
|
||||
Heading,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Spacer,
|
||||
Text,
|
||||
Textarea,
|
||||
useDisclosure,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { useState } from "react";
|
||||
import Login from "../components/Login";
|
||||
import Success from "../components/Success";
|
||||
|
||||
export function Page(pageProps: { [p: string]: any }) {
|
||||
if (!pageProps.logged_in) return <Login />;
|
||||
|
||||
const { isOpen, onClose, onOpen } = useDisclosure();
|
||||
const [showSuccess, setShowSuccess] = useState(false);
|
||||
const toast = useToast();
|
||||
|
||||
async function submit() {
|
||||
const learned = (document.getElementById("learned") as HTMLInputElement)
|
||||
.value;
|
||||
const whyBanned = (document.getElementById("whyBanned") as HTMLInputElement)
|
||||
.value;
|
||||
const whyUnban = (document.getElementById("whyUnban") as HTMLInputElement)
|
||||
.value;
|
||||
|
||||
const submitReq = await fetch("/api/appeals/submit", {
|
||||
body: JSON.stringify({
|
||||
learned,
|
||||
whyBanned,
|
||||
whyUnban,
|
||||
}),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
}).catch(() => {});
|
||||
|
||||
if (!submitReq)
|
||||
return toast({
|
||||
description: "Please check your internet and try again",
|
||||
duration: 10000,
|
||||
isClosable: true,
|
||||
status: "error",
|
||||
title: "Request Failed",
|
||||
});
|
||||
|
||||
if (!submitReq.ok)
|
||||
return toast({
|
||||
description: ((await submitReq.json()) as { error: string }).error,
|
||||
duration: 10000,
|
||||
isClosable: true,
|
||||
status: "error",
|
||||
title: "Error",
|
||||
});
|
||||
|
||||
setShowSuccess(true);
|
||||
}
|
||||
|
||||
async function toggle(active: boolean) {
|
||||
const toggleReq = await fetch("/api/appeals/toggle", {
|
||||
body: JSON.stringify({ active }),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
if (!toggleReq.ok)
|
||||
return toast({
|
||||
description: ((await toggleReq.json()) as { error: string }).error,
|
||||
duration: 10000,
|
||||
isClosable: true,
|
||||
status: "error",
|
||||
title: "Error",
|
||||
});
|
||||
|
||||
toast({
|
||||
description: `The appeals form is now ${active ? "opened" : "closed"}.`,
|
||||
isClosable: true,
|
||||
status: "success",
|
||||
title: `Appeals ${active ? "enabled" : "disabled"}`,
|
||||
});
|
||||
|
||||
onClose();
|
||||
await new Promise((p) => setTimeout(p, 5000));
|
||||
}
|
||||
|
||||
return showSuccess ? (
|
||||
<Success
|
||||
heading="Appeal Submitted"
|
||||
message="You will receive an email when we reach a decision."
|
||||
/>
|
||||
) : (
|
||||
<Container maxW="container.md" pt="4vh" textAlign="start">
|
||||
<Alert
|
||||
borderRadius="8px"
|
||||
display={pageProps.disabled ? "flex" : "none"}
|
||||
mb="16px"
|
||||
status="error"
|
||||
>
|
||||
<AlertIcon />
|
||||
<Box>
|
||||
<AlertTitle>Appeals Closed</AlertTitle>
|
||||
<AlertDescription>
|
||||
We are currently not accepting appeals.
|
||||
</AlertDescription>
|
||||
</Box>
|
||||
</Alert>
|
||||
<Flex>
|
||||
<Spacer />
|
||||
<Button display={pageProps.can_toggle ? "" : "none"} onClick={onOpen}>
|
||||
{pageProps.disabled ? "Enable" : "Disable"} Appeals
|
||||
</Button>
|
||||
</Flex>
|
||||
<br />
|
||||
<Modal isCentered isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Toggle appeals?</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<Text>
|
||||
Are you sure you want to{" "}
|
||||
{pageProps.disabled ? "enable" : "disable"} appeals?
|
||||
</Text>
|
||||
</ModalBody>
|
||||
<ModalFooter style={{ gap: "8px" }}>
|
||||
<Button onClick={onClose} variant="ghost">
|
||||
No
|
||||
</Button>
|
||||
<Button
|
||||
onClick={async () => await toggle(pageProps.disabled)}
|
||||
variant="danger"
|
||||
>
|
||||
Yes
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<Heading size="xl">Discord Appeals</Heading>
|
||||
<br />
|
||||
<Text fontSize="md">
|
||||
This is for Discord bans only! See the support page if you were banned
|
||||
from the game.
|
||||
</Text>
|
||||
<br />
|
||||
<br />
|
||||
<Heading size="md">Why were you banned?</Heading>
|
||||
<br />
|
||||
<Textarea
|
||||
disabled={!pageProps.can_appeal}
|
||||
id="whyBanned"
|
||||
maxLength={500}
|
||||
placeholder="Your response"
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<Heading size="md">Why should we unban you?</Heading>
|
||||
<br />
|
||||
<Textarea
|
||||
disabled={!pageProps.can_appeal}
|
||||
id="whyUnban"
|
||||
maxLength={2000}
|
||||
placeholder="Your response"
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<Heading size="md">What have you learned from your mistake?</Heading>
|
||||
<br />
|
||||
<Textarea
|
||||
disabled={!pageProps.can_appeal}
|
||||
id="learned"
|
||||
maxLength={2000}
|
||||
placeholder="Your response"
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<Button
|
||||
disabled={pageProps.disabled || pageProps.already_submitted}
|
||||
onClick={async () => await submit()}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
18
pages/index.page.tsx
Normal file
18
pages/index.page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Box, Container, Text } from "@chakra-ui/react";
|
||||
|
||||
export function Page() {
|
||||
return (
|
||||
<>
|
||||
<Box alignContent="left">
|
||||
<Container maxW="container.lg" paddingTop="8vh" textAlign="left">
|
||||
<Text>
|
||||
srfidukjghdiuftgrteutgrtsu,k jhsrte h hjgtsredbfdgns srthhfg h fgdyh
|
||||
y
|
||||
</Text>
|
||||
</Container>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const title = "Home - Car Crushers";
|
||||
20
pages/mod-queue.page.server.tsx
Normal file
20
pages/mod-queue.page.server.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
export async function onBeforeRender(pageContext: PageContext) {
|
||||
const typePermissions = {
|
||||
appeal: [1 << 0, 1 << 1],
|
||||
gma: [1 << 5],
|
||||
report: [1 << 5],
|
||||
};
|
||||
const { searchParams } = new URL(
|
||||
pageContext.urlOriginal,
|
||||
"http://localhost:8788"
|
||||
);
|
||||
const includeClosed = searchParams.get("includeClosed");
|
||||
const type = searchParams.get("type");
|
||||
const sort = searchParams.get("sort") ?? "asc";
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
pageProps: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
40
pages/mod-queue.page.tsx
Normal file
40
pages/mod-queue.page.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
Flex,
|
||||
Select,
|
||||
useBreakpointValue,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function Page(pageProps: { [p: string]: any }) {
|
||||
const isDesktop = useBreakpointValue({ base: false, lg: true });
|
||||
const entryTypes = [];
|
||||
|
||||
for (const type of pageProps.entry_types)
|
||||
entryTypes.push(<option value={type.value}>{type.name}</option>)
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
const queueRequest = await fetch("/api/mod-queue")
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container maxW="container.xl">
|
||||
<Flex>
|
||||
<VStack>
|
||||
|
||||
</VStack>
|
||||
<Box display={ isDesktop ? undefined : "none" } w="250px">
|
||||
<Select placeholder="Entry Type">
|
||||
<option value="">All</option>
|
||||
{entryTypes}
|
||||
</Select>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
export const title = "Mod Queue - Car Crushers";
|
||||
238
pages/privacy.page.tsx
Normal file
238
pages/privacy.page.tsx
Normal file
@@ -0,0 +1,238 @@
|
||||
import { Container, Heading, Link, Text } from "@chakra-ui/react";
|
||||
|
||||
export function Page() {
|
||||
return (
|
||||
<Container maxW="container.lg" pb="8vh" pt="4vh" textAlign="start">
|
||||
<Heading>Privacy Policy</Heading>
|
||||
<br />
|
||||
<Text>Last Updated: 2023-01-07</Text>
|
||||
<br />
|
||||
<hr />
|
||||
<br />
|
||||
<Heading size="lg">Information We Collect</Heading>
|
||||
<br />
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Discord Profile Information</strong>: We receive account
|
||||
information from Discord, Inc. when you sign in such as your username,
|
||||
discriminator, avatar, and banner in order to authenticate you and
|
||||
authorize requests. A list of information available can be found at{" "}
|
||||
<Link
|
||||
color="#646cff"
|
||||
href="https://discord.com/developers/docs/resources/use"
|
||||
target="_blank"
|
||||
>
|
||||
Discord's Developer Portal
|
||||
</Link>
|
||||
.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Email Addresses</strong>: We receive your email address from
|
||||
your Discord account in order to allow us to contact you as necessary.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Member Information from Our Discord Server</strong>: We
|
||||
receive a list of your roles within our Discord Server in order to
|
||||
determine your ability to access certain parts of this site.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Error Reports</strong>: We collect error reports to aid in
|
||||
fixing bugs and ensuring site stability.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Uploaded Files and Reports</strong>: We store uploaded files
|
||||
and user reports to aid in moderating exploiters from Car Crushers 2.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Appeals</strong>: We store appeal requests to review them.
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<Heading size="lg">
|
||||
Legal Basis for Processing of Your Personal Data
|
||||
</Heading>
|
||||
<br />
|
||||
<ul>
|
||||
<li>
|
||||
<strong>To fulfill contractual commitments</strong>: E.g allow you to
|
||||
use the site as intended.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Legitimate interests</strong>: In some cases, we continue to
|
||||
process data on the grounds that our legitimate interests override the
|
||||
interests or rights and freedoms of affected individuals. These
|
||||
interests may include but are not limited to: Protecting ourselves and
|
||||
our users, and preventing spam.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Consent</strong>: Where required by law, and in some other
|
||||
cases, we handle personal data on the basis of your implied or express
|
||||
consent.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Legal Requirements</strong>: We need to use and disclose
|
||||
personal data in certain ways to comply with our legal obligations.
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<Heading size="lg">Disclosure of Your Information</Heading>
|
||||
<br />
|
||||
<Text>
|
||||
While we have no intention of giving your personal data to Mark
|
||||
Zuckerberg, in certain circumstances we may share your information with
|
||||
third parties, as set forth below.
|
||||
</Text>
|
||||
<br />
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Business Transfers</strong>: In the event of a corporate sale,
|
||||
merger, reorganization, bankruptcy, dissolution or etc., your
|
||||
information may be part of the transferred assets.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Consent</strong>: We may transfer your information with your
|
||||
consent.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Agents and Related Third Parties</strong>: We enlist the help
|
||||
of other entities to perform certain tasks related to this site, such
|
||||
as: Data Storage, Error Tracking and Reporting, and Hosting.
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<Heading size="lg">Our Partners</Heading>
|
||||
<br />
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Cloudflare, Inc. (San Francisco, CA)</strong>: Hosting and
|
||||
data storage provider.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Google, LLC. (Mountain View, CA)</strong>: File storage.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Mailgun Technologies, Inc. (San Antonio, TX)</strong>: Email
|
||||
solutions.
|
||||
</li>
|
||||
<li>
|
||||
<strong>
|
||||
Functional Software, Inc. d/b/a Sentry (San Francisco, CA)
|
||||
</strong>
|
||||
: Error reporting.
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<Heading size="lg">Security</Heading>
|
||||
<br />
|
||||
<Text>
|
||||
We take reasonable steps to protect the information provided via the
|
||||
Services from loss, misuse, and unauthorized access, disclosure,
|
||||
alteration, or destruction. However, no Internet or email transmission
|
||||
is ever fully secure or error free. In particular, email sent to or from
|
||||
the Services may not be secure. Therefore, you should take special care
|
||||
in deciding what information you send to us via email or forms. Please
|
||||
keep this in mind when disclosing any information via the Internet.
|
||||
</Text>
|
||||
<br />
|
||||
<Heading size="lg">Cookies</Heading>
|
||||
<br />
|
||||
<Text>
|
||||
All cookies by this site are necessary for the site to function. We do
|
||||
not set any marketing or tracking cookies.
|
||||
</Text>
|
||||
<br />
|
||||
<Heading size="lg">Children</Heading>
|
||||
<br />
|
||||
<Text>
|
||||
Our services are meant for users who meet or exceed the age of digital
|
||||
consent under relevant laws (such as COPPA and GDPR).
|
||||
</Text>
|
||||
<br />
|
||||
<Heading size="lg">Links to Other Websites</Heading>
|
||||
<br />
|
||||
<Text>
|
||||
This privacy policy only applies to this site and not other sites.
|
||||
</Text>
|
||||
<br />
|
||||
<Heading size="lg">Data Retention</Heading>
|
||||
<br />
|
||||
<Text>
|
||||
All data is retained as stated in the section below and is necessary to
|
||||
conduct operations.
|
||||
</Text>
|
||||
<br />
|
||||
<Heading size="lg">Data Retention Periods</Heading>
|
||||
<br />
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Session Data</strong>: Maximum of one week (or on logout).
|
||||
</li>
|
||||
<li>
|
||||
<strong>Email Addresses</strong>: From the time that you submit a form
|
||||
to the time that it is marked as closed (i.e when your appeal is
|
||||
reviewed).
|
||||
</li>
|
||||
<li>
|
||||
<strong>Profile, Server, and Permission Data</strong>: See the Session
|
||||
Data entry.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Error Reports</strong>: Error reports collected by Sentry are
|
||||
deleted after 90 days. Sensitive information such as IP addresses are
|
||||
not retained with logs.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Uploaded Files and Report Submissions</strong>: Report
|
||||
submissions and uploaded media as part of a report submission may be
|
||||
retained indefinitely. Email addresses are removed after report
|
||||
review.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Appeals</strong>: Appeal submissions are retained for 3 years
|
||||
after closure date.
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<Heading size="lg">Data Rights and Choices</Heading>
|
||||
<br />
|
||||
<Text>You have the right to:</Text>
|
||||
<br />
|
||||
<ul>
|
||||
<li>Access your personal information</li>
|
||||
<li>Delete your personal information</li>
|
||||
<li>
|
||||
Request restrictions on the processing of your personal information
|
||||
</li>
|
||||
<li>Lodge a complaint with a supervisory authority</li>
|
||||
</ul>
|
||||
<br />
|
||||
<Text>
|
||||
Some information as specified in the Data Retention section may be
|
||||
retained even after deleting your data. The rights and options listed
|
||||
above are subject to limitations based on applicable law.
|
||||
</Text>
|
||||
<br />
|
||||
<Heading size="lg">Contact Us</Heading>
|
||||
<br />
|
||||
<Text>
|
||||
By Email:{" "}
|
||||
<Link color="#646cff" href="mailto:privacy@ccdiscussion.com">
|
||||
privacy@ccdiscussion.com
|
||||
</Link>
|
||||
</Text>
|
||||
<br />
|
||||
<Text>
|
||||
Discord:{" "}
|
||||
<Link
|
||||
color="#646cff"
|
||||
href="https://discord.com/invite/carcrushers"
|
||||
target="_blank"
|
||||
>
|
||||
https://discord.com/invite/carcrushers
|
||||
</Link>
|
||||
</Text>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export const title = "Privacy - Car Crushers";
|
||||
13
pages/report.page.server.tsx
Normal file
13
pages/report.page.server.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
export async function onBeforeRender(pageContext: PageContext) {
|
||||
return {
|
||||
pageContext: {
|
||||
pageProps: {
|
||||
logged_in: Boolean(pageContext.current_user),
|
||||
},
|
||||
status: pageContext.current_user ? 200 : 401,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const description = "Found a cheater in Car Crushers 2? Report them here.";
|
||||
export const title = "Report - Car Crushers";
|
||||
230
pages/report.page.tsx
Normal file
230
pages/report.page.tsx
Normal file
@@ -0,0 +1,230 @@
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
CircularProgressLabel,
|
||||
Container,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Heading,
|
||||
HStack,
|
||||
Input,
|
||||
Link,
|
||||
Text,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Login from "../components/Login";
|
||||
import Success from "../components/Success";
|
||||
|
||||
export function Page(pageProps: { [p: string]: any }) {
|
||||
if (!pageProps.logged_in) return <Login />;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
138
pages/support.page.tsx
Normal file
138
pages/support.page.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Box,
|
||||
Container,
|
||||
Heading,
|
||||
Link,
|
||||
Spacer,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
export function Page() {
|
||||
return (
|
||||
<Container
|
||||
borderRadius="12px"
|
||||
borderWidth="1px"
|
||||
maxW="container.md"
|
||||
mt="8vh"
|
||||
>
|
||||
<VStack w="100%" spacing={3}>
|
||||
<Spacer />
|
||||
<Heading alignSelf="start" pl="2.5%" size="md">
|
||||
What do you need help with?
|
||||
</Heading>
|
||||
<Spacer />
|
||||
<Accordion textAlign="left" w="100%">
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
I want to report someone exploiting
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
To report a player,{" "}
|
||||
<Link color="#646cff" href="/report">
|
||||
head to our report page.
|
||||
</Link>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
I want a data rollback or transfer
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
Please join our{" "}
|
||||
<Link
|
||||
color="#646cff"
|
||||
href="https://discord.com/invite/carcrushers"
|
||||
>
|
||||
Discord server
|
||||
</Link>{" "}
|
||||
and contact ModMail.
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
I want to appeal my ban
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
If you were banned from our Discord server,{" "}
|
||||
<Link color="#646cff" href="/appeals">
|
||||
use this form
|
||||
</Link>
|
||||
. If you were banned from the game,{" "}
|
||||
<Link
|
||||
color="#646cff"
|
||||
href="https://www.roblox.com/games/527921900/Car-Crushers-2-Appeals"
|
||||
>
|
||||
fill out the form here
|
||||
</Link>
|
||||
.
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
I want to apply for a staff position
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
Most staff position openings will be announced in our{" "}
|
||||
<Link
|
||||
color="#646cff"
|
||||
href="https://discord.com/invite/carcrushers"
|
||||
>
|
||||
Discord server
|
||||
</Link>
|
||||
. Forum mod openings are generally announced on the forum.
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
My problem is not listed
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
Join our{" "}
|
||||
<Link
|
||||
color="#646cff"
|
||||
href="https://discord.com/invite/carcrushers"
|
||||
>
|
||||
Discord server
|
||||
</Link>{" "}
|
||||
and open a ticket with ModMail.
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<Spacer />
|
||||
<Spacer />
|
||||
</VStack>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export const title = "Support - Car Crushers";
|
||||
56
pages/team.page.tsx
Normal file
56
pages/team.page.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
Card,
|
||||
CardFooter,
|
||||
Code,
|
||||
Container,
|
||||
Divider,
|
||||
Heading,
|
||||
Image,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import team from "../data/team.json";
|
||||
|
||||
export function Page() {
|
||||
return (
|
||||
<Container maxW="container.xl" pt="4vh">
|
||||
<Heading textAlign="start">Our Team</Heading>
|
||||
<br />
|
||||
<Text textAlign="start">
|
||||
Please respect our staff, and <u>do not send direct messages</u> or
|
||||
friend requests in place of official channels.
|
||||
</Text>
|
||||
<br />
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gap: "1rem",
|
||||
gridTemplateColumns: "repeat(auto-fill, minmax(16rem, 1fr))",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{team.map((member) => (
|
||||
<Card key={member.id} maxW="xs" p="12px">
|
||||
<Image
|
||||
alt={member.tag + "'s avatar"}
|
||||
borderRadius="50%"
|
||||
src={`/files/avatars/${member.id}.webp`}
|
||||
/>
|
||||
<Stack mb="8" mt="6" spacing="3">
|
||||
<b>
|
||||
<Heading size="md">{member.tag}</Heading>
|
||||
</b>
|
||||
<Text>{member.position}</Text>
|
||||
</Stack>
|
||||
<Divider />
|
||||
<CardFooter justifyContent="center">
|
||||
<Code>ID: {member.id}</Code>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export const title = "Team - Car Crushers";
|
||||
122
pages/terms.page.tsx
Normal file
122
pages/terms.page.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import { Container, Heading, Link, Text } from "@chakra-ui/react";
|
||||
|
||||
export function Page() {
|
||||
return (
|
||||
<Container maxW="container.lg" pb="8vh" pt="4vh" textAlign="start">
|
||||
<Heading>Terms and Conditions</Heading>
|
||||
<br />
|
||||
<Text>Last Updated: 2023-01-07</Text>
|
||||
<br />
|
||||
<Text>Yes, we know this shit is boring to read, but it's important.</Text>
|
||||
<br />
|
||||
<hr />
|
||||
<br />
|
||||
<Text>These terms govern your use of the Car Crushers website.</Text>
|
||||
<br />
|
||||
<Text>
|
||||
You would think people have common sense but sadly many don't.
|
||||
</Text>
|
||||
<br />
|
||||
<Text>For this reason, we have to create this document.</Text>
|
||||
<br />
|
||||
<br />
|
||||
<Heading size="lg">Definitions</Heading>
|
||||
<br />
|
||||
<ul>
|
||||
<li>We, Us: Car Crushers (the operator of this website)</li>
|
||||
<li>You: The person currently reading this document</li>
|
||||
</ul>
|
||||
<br />
|
||||
<br />
|
||||
<Heading size="lg">General Rules</Heading>
|
||||
<br />
|
||||
<ul>
|
||||
<li>Do not upload malicious files to this site</li>
|
||||
<li>Do not submit spam using forms on this site</li>
|
||||
<li>Do not upload any content illegal under the laws of Sweden</li>
|
||||
<li>You must be at least 13 years old to use this site</li>
|
||||
<li>
|
||||
You may not automate access to this site by any means (except a public
|
||||
search crawler if you operate one)
|
||||
</li>
|
||||
<li>
|
||||
You may not falsely imply that you are affiliated with or endorsed by
|
||||
Car Crushers
|
||||
</li>
|
||||
<li>
|
||||
<Link color="#646cff" href="/files/why.jpg" target="_blank">
|
||||
All visitors from New Jersey must explain why
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<br />
|
||||
<Heading size="lg">Enforcement</Heading>
|
||||
<br />
|
||||
<Text>
|
||||
We may investigate and prosecute violations of these terms to the
|
||||
fullest legal extent. We may notify and cooperate with law enforcement
|
||||
authorities in prosecuting violations of the law and these terms.
|
||||
</Text>
|
||||
<br />
|
||||
<br />
|
||||
<Heading size="lg">Your Content</Heading>
|
||||
<br />
|
||||
<Text>
|
||||
Nothing in these terms grant us ownership rights to any content that you
|
||||
submit to this site. Nothing in these terms grants you ownership rights
|
||||
to our intellectual property either. Any content you submit to this site
|
||||
is your responsibility. Content you submit to us belongs to you. But at
|
||||
a minimum, you license us to store the content and display it to
|
||||
authorized users.
|
||||
</Text>
|
||||
<br />
|
||||
<br />
|
||||
<Heading size="lg">Warranty and Disclaimer</Heading>
|
||||
<br />
|
||||
<Text>
|
||||
WE DO NOT GUARANTEE A BUG-FREE SITE, THAT IS IMPOSSIBLE. THERE IS
|
||||
ABSOLUTELY NO WARRANTY WHATSOEVER, EXPRESS OR IMPLIED. YOUR USE OF THIS
|
||||
SITE IS AT YOUR OWN RISK. THERE ARE NO GUARANTEES ON ANYTHING, NOT EVEN
|
||||
THAT THIS SITE WILL EXIST TOMORROW.
|
||||
</Text>
|
||||
<br />
|
||||
<br />
|
||||
<Heading size="lg">Termination</Heading>
|
||||
<br />
|
||||
<Text>
|
||||
We may terminate or suspend your access to the Service immediately,
|
||||
without prior notice or liability, under our sole discretion, for any
|
||||
reason whatsoever and without limitation, including but not limited to a
|
||||
breach of the Terms.
|
||||
</Text>
|
||||
<br />
|
||||
<Text>
|
||||
The contract is fully terminated when all user data (including every
|
||||
copy of every file uploaded) is fully deleted from our service.
|
||||
</Text>
|
||||
<br />
|
||||
<br />
|
||||
<Heading size="lg">Indemnification</Heading>
|
||||
<br />
|
||||
<Text>
|
||||
You agree to defend, indemnify and hold harmless Car Crushers and its
|
||||
licensee and licensors, and their employees, contractors, agents,
|
||||
officers and directors, from and against any and all claims, damages,
|
||||
obligations, losses, liabilities, costs or debt, and expenses (including
|
||||
but not limited to attorney's fees), resulting from or arising out of a)
|
||||
your use and access of the Service, or b) a breach of these Terms.
|
||||
</Text>
|
||||
<br />
|
||||
<br />
|
||||
<Heading size="lg">Governing Law</Heading>
|
||||
<br />
|
||||
<Text>
|
||||
These terms shall be governed and construed in accordance with the laws
|
||||
of Västmanland, Sweden.
|
||||
</Text>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export const title = "Terms - Car Crushers";
|
||||
Reference in New Issue
Block a user