Compare commits

...

46 Commits

Author SHA1 Message Date
017ce48835 Finally fix leaderboard hiding 2025-11-24 12:02:16 -05:00
72f8791276 Bump packages 2025-10-23 16:29:33 -04:00
63b1926056 Strip out datacenter code from report ids 2025-10-23 15:34:36 -04:00
25dc50e05c Space out departments 2025-10-23 15:28:34 -04:00
a403832735 Only apply decisions to departments the notice covers 2025-10-23 15:22:45 -04:00
e1f1093d84 Hopefully last one 2025-10-14 02:45:06 -04:00
243a4d1ceb Even more le fixes 2025-10-14 02:38:31 -04:00
54d9bd5e82 More le fixes 2025-10-14 02:28:22 -04:00
a1f4b4eabe Le fixes 2025-10-14 02:14:41 -04:00
64edc6169b New page 2025-10-14 02:10:25 -04:00
cf84722418 Remove old game mod management modal 2025-10-14 02:10:07 -04:00
102b29c54f Bump node version 2025-10-13 13:52:39 -04:00
42f13612b1 Make note creation 2025-10-13 13:49:30 -04:00
b1448ac30e Add user note management endpoints 2025-10-13 12:19:44 -04:00
bd053908a4 Add name to gme keys 2025-10-13 12:18:57 -04:00
cae1b523f5 Bump dependencies 2025-10-13 02:23:47 -04:00
e4b31efa72 You will not be missed 2025-10-13 02:18:37 -04:00
5d249a7069 Bump .node-version 2025-09-11 15:42:35 -04:00
56c5e6dc0f Fix object-stringifying of things that shouldn't be stringified 2025-09-11 15:41:04 -04:00
f2c332b649 Update some dependencies 2025-09-11 15:41:04 -04:00
b8bc4acf4b Replace remaining hex colors with base 10 colors 2025-08-31 00:47:12 -04:00
5bb538c790 More CSS fixes 2025-08-17 11:52:33 -04:00
7aa1d27844 CSS fixes for under-heading info 2025-08-17 11:29:43 -04:00
3c5c51b5da Update dependencies for once 2025-08-16 23:49:34 -04:00
74c0e52a80 Unhide new punishment type 2025-08-16 23:38:20 -04:00
7b68f14c2e New option type 2025-08-16 23:36:34 -04:00
85871fc90a Enforce serverside check of specified punishment type 2025-08-07 03:35:41 -04:00
41f361fcf1 Accept type property in appeal submissions 2025-08-07 02:28:15 -04:00
513234bbe6 Send back appeal types in metadata response 2025-08-07 02:24:04 -04:00
5ba59ff596 Accept multiple types of appeals 2025-08-03 01:14:02 -04:00
bcd07299c5 Display new statuses on hammer page 2025-08-03 01:13:38 -04:00
7c7701f406 Fix reporting 2025-08-03 00:10:21 -04:00
fcf3c95a0a Add new properties in case acceptance 2025-07-29 04:39:06 -04:00
398b2a938a Fix alignment of report type selection 2025-07-27 03:31:11 -04:00
fe64e33c1d Don't display options on website yet 2025-07-27 01:00:28 -04:00
edc392fba0 Add server configurator block option 2025-07-27 00:54:42 -04:00
62319a84d1 Apply hack to display images 2025-07-27 00:52:44 -04:00
195af5e124 Bump node version 2025-07-27 00:37:27 -04:00
f9d9bdbac6 Enforce submission type in upload endpoint 2025-07-27 00:25:59 -04:00
451d1c93d4 Allow images in upload signer 2025-07-27 00:24:59 -04:00
157c9b188b Allow images on frontend 2025-07-27 00:09:30 -04:00
8d357ec249 Add report submission type radio 2025-07-27 00:03:10 -04:00
ba8e1bab7c Account for new ET quota requirements 2025-06-30 23:46:14 -04:00
68898ba654 Update repo link in readme 2025-06-20 11:49:52 -04:00
44ab73c8a5 Fix date nonsense 2025-06-19 16:29:38 -04:00
98c662ed9b Account for funny JS months 2025-06-19 16:08:01 -04:00
33 changed files with 711 additions and 415 deletions

View File

@@ -1 +1 @@
v22.13.1
v22.20.0

View File

@@ -7,7 +7,7 @@ Please don't complain about your ban on this repo.
## Get the Code
`git clone https://teamhydra.io/regalijan/car-crushers-portal.git`
`git clone https://git.hep.gg/regalijan/car-crushers-portal.git`
## Make it Larger

View File

@@ -76,12 +76,15 @@ export async function loader({ context }: { context: RequestContext }) {
for (const member of Object.keys(memberMap))
if (
memberMap[member].points < 30 &&
(memberMap[member].points < 50 ||
eventsQuery.results.filter(
(e) => e.type === "gamenight" && e.created_by === member,
).length === 0) &&
!inactivityQuery.results.find(
(i) => i.uid === member && JSON.parse(i.decisions).et,
)
)
memberMap[member].points -= 30;
memberMap[member].points -= 50;
return {
members: memberMap,

209
app/routes/gmm.tsx Normal file
View File

@@ -0,0 +1,209 @@
import {
Button,
Container,
FormControl,
FormLabel,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Table,
TableCaption,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr,
useDisclosure,
useToast,
} from "@chakra-ui/react";
import { useLoaderData } from "@remix-run/react";
import { useState } from "react";
export async function loader({ context }: { context: RequestContext }) {
if (!context.data.current_user)
throw new Response(null, {
status: 401,
});
if (
![
"165594923586945025",
"289372404541554689",
"396347223736057866",
].includes(context.data.current_user.id)
)
throw new Response(null, {
status: 403,
});
return (await context.env.DATA.list({ prefix: "gamemod_" }))?.keys ?? [];
}
export default function () {
const data: { [k: string]: any }[] = useLoaderData<typeof loader>();
const [gameModData, setGameModData] = useState(data);
const { isOpen, onClose, onOpen } = useDisclosure();
const [idToAdd, setIdToAdd] = useState<string>("");
const [nameToAdd, setNameToAdd] = useState<string>("");
const toast = useToast();
async function addMod(id: string, name: string) {
const response = await fetch("/api/gme/add", {
body: JSON.stringify({ name, user: id }),
headers: {
"content-type": "application/json",
},
method: "POST",
});
if (!response.ok) {
let msg = "Unknown error";
try {
msg = ((await response.json()) as { error: string }).error;
} catch {}
toast({
description: msg,
status: "error",
title: "Cannot add game mod",
});
} else {
onClose();
location.reload();
}
setIdToAdd("");
setNameToAdd("");
}
async function removeMod(user: string) {
const response = await fetch("/api/gme/remove", {
body: JSON.stringify({ user }),
headers: {
"content-type": "application/json",
},
method: "POST",
});
if (!response.ok) {
let msg = "Unknown error";
try {
msg = ((await response.json()) as { error: string }).error;
} catch {}
toast({
description: msg,
status: "error",
title: "Cannot remove game mod",
});
return;
}
toast({
description: `${data.find((i) => i.metadata.id === user)?.name} was removed as a game mod`,
status: "success",
title: "Game mod removed",
});
setGameModData(gameModData.filter((i) => i.metadata.id !== user));
}
return (
<Container maxW="container.lg">
<TableContainer>
<Table variant="simple">
<TableCaption>Currently active game mods</TableCaption>
<Thead>
<Tr>
<Th>ID</Th>
<Th>Name</Th>
<Th>Added At</Th>
<Th>Added By</Th>
<Th>Remove</Th>
</Tr>
</Thead>
<Tbody>
{gameModData.map((item) => {
return (
<Tr key={item.metadata.id}>
<Td>{item.metadata.id}</Td>
<Td>{item.metadata.name}</Td>
<Td>{item.metadata.created_at}</Td>
<Td>{item.metadata.created_by}</Td>
<Td>
<Button
onClick={async () => await removeMod(item.metadata.id)}
>
Remove
</Button>
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
<Button alignSelf="end" mt="16px" onClick={onOpen}>
Add
</Button>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Add Game Mod</ModalHeader>
<ModalCloseButton />
<ModalBody>
<FormControl>
<FormLabel>Name</FormLabel>
<Input
maxLength={32}
onChange={(e) => setNameToAdd(e.target.value)}
value={nameToAdd}
/>
</FormControl>
<br />
<FormControl pt="16px">
<FormLabel>ID</FormLabel>
<Input
maxLength={19}
onChange={(e) => setIdToAdd(e.target.value)}
value={idToAdd}
/>
</FormControl>
</ModalBody>
<ModalFooter gap="8px">
<Button
onClick={() => {
onClose();
setIdToAdd("");
setNameToAdd("");
}}
>
Cancel
</Button>
<Button
colorScheme="blue"
disabled={
!idToAdd.match(/\d{17,19}/) ||
nameToAdd.length < 1 ||
nameToAdd.length > 32
}
onClick={async () => {
await addMod(idToAdd, nameToAdd);
}}
>
Add
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</Container>
);
}

View File

@@ -169,20 +169,22 @@ export default function () {
Decisions:
<br />
Approved:
{Object.keys(currentInactivity.decisions ?? {}).filter(
(d) => currentInactivity.decisions[d],
)}
{Object.keys(currentInactivity.decisions ?? {})
.filter((d) => currentInactivity.decisions[d])
.join(", ")}
<br />
Denied:
{Object.keys(currentInactivity.decisions ?? {}).filter(
(d) => !currentInactivity.decisions[d],
)}
{Object.keys(currentInactivity.decisions ?? {})
.filter((d) => !currentInactivity.decisions[d])
.join(", ")}
<br />
Pending:
{currentInactivity.departments?.filter(
(d: "DM" | "ET" | "FM" | "WM") =>
typeof currentInactivity.decisions[d] === "undefined",
)}
{currentInactivity.departments
?.filter(
(d: "DM" | "ET" | "FM" | "WM") =>
typeof currentInactivity.decisions[d] === "undefined",
)
.join(", ")}
</ListItem>
</UnorderedList>
</ModalBody>

View File

@@ -30,7 +30,6 @@ import { useLoaderData } from "@remix-run/react";
import AppealBans from "../../components/AppealBans.js";
import AppealCard from "../../components/AppealCard.js";
import GameAppealCard from "../../components/GameAppealCard.js";
import GameModManagementModal from "../../components/GameModManagementModal.js";
import NewGameBan from "../../components/NewGameBan.js";
import NewInfractionModal from "../../components/NewInfractionModal.js";
import ReportCard from "../../components/ReportCard.js";
@@ -151,7 +150,7 @@ export default function () {
useEffect(() => {
if (messageChannel.current) {
messageChannel.current.port1.onmessage = function (ev) {
messageChannel.current.port1.onmessage = function (ev: any) {
const { data }: { data: string } = ev;
setEntries([...entries].filter((entry) => entry.id !== data));
@@ -337,7 +336,11 @@ export default function () {
},
appeal_bans: useDisclosure(),
game_ban: useDisclosure(),
gme: useDisclosure(),
gme: {
isOpen: false,
onClose: () => {},
onOpen: () => location.assign("/gmm"),
},
inactivity: useDisclosure(),
infraction: useDisclosure(),
user_lookup: {
@@ -431,10 +434,6 @@ export default function () {
isOpen={itemModals.appeal_bans.isOpen}
onClose={itemModals.appeal_bans.onClose}
/>
<GameModManagementModal
isOpen={itemModals.gme.isOpen}
onClose={itemModals.gme.onClose}
/>
<NewGameBan
isOpen={itemModals.game_ban.isOpen}
onClose={itemModals.game_ban.onClose}

View File

@@ -9,6 +9,9 @@ import {
HStack,
Input,
Link,
Radio,
RadioGroup,
Stack,
Text,
Textarea,
useToast,
@@ -31,28 +34,34 @@ export async function loader({
export function meta() {
return [
{
title: "Report an Exploiter - Car Crushers",
title: "Send a Report - Car Crushers",
},
{
name: "description",
content: "Use this page to report a cheater",
content: "Use this page to submit a report",
},
];
}
export default function () {
const [fileProgress, setFileProgress] = useState(0);
const [submissionType, setSubmissionType] = useState("exploit");
const [showSuccess, setShowSuccess] = useState(false);
const toast = useToast();
const [uploading, setUploading] = useState(false);
const [loading, setLoading] = useState(false);
const fileTypes: { [k: string]: string } = {
avif: "image/avif",
gif: "image/gif",
jpg: "image/jpeg",
jpeg: "image/jpeg",
m4v: "video/x-m4v",
mkv: "video/x-matroska",
mov: "video/mp4",
mp4: "video/mp4",
png: "image/png",
webm: "video/webm",
webp: "image/webp",
wmv: "video/x-ms-wmv",
};
@@ -132,6 +141,7 @@ export default function () {
body: JSON.stringify({
description: description || undefined,
files: filelist,
submissionType,
turnstileResponse: logged_in ? undefined : turnstileToken,
usernames,
}),
@@ -276,6 +286,19 @@ export default function () {
</FormLabel>
<Input id="usernames" placeholder="builderman" />
</FormControl>
<FormControl isRequired>
<FormLabel htmlFor="submissionType">Type of Report</FormLabel>
<RadioGroup
id="submissionType"
onChange={setSubmissionType}
value={submissionType}
>
<Stack spacing={2}>
<Radio value="exploit">Exploiter</Radio>
<Radio value="abuse">Server Configurator Abuse</Radio>
</Stack>
</RadioGroup>
</FormControl>
<br />
<FormControl isRequired>
<FormLabel>Your Evidence (Max size per file: 512MB)</FormLabel>

View File

@@ -84,6 +84,10 @@ export default function (props: GameAppealProps & { port?: MessagePort }) {
</CardHeader>
<CardBody>
<Stack divider={<StackDivider />}>
<Box>
<Heading size="xs">Appeal Type</Heading>
<Text>{props.type}</Text>
</Box>
<Box>
<Heading size="xs">Response: Explanation of Ban</Heading>
<Text>{props.what_happened}</Text>

View File

@@ -1,157 +0,0 @@
import {
Button,
HStack,
Input,
Link,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Table,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr,
useToast,
} from "@chakra-ui/react";
import { useEffect, useState } from "react";
export default function (props: { isOpen: boolean; onClose: () => void }) {
const [mods, setMods] = useState([]);
const [userToAdd, setUserToAdd] = useState("");
const toast = useToast();
useEffect(() => {
if (!props.isOpen) return;
(async function () {
const gmeResp = await fetch("/api/gme/list");
if (!gmeResp.ok) {
toast({
description: "Failed to load GME data",
status: "error",
title: "Oops",
});
return;
}
setMods(await gmeResp.json());
})();
}, [props.isOpen]);
async function addUser() {
if (!userToAdd || !userToAdd.match(/^\d{17,19}$/)) {
toast({
description: "Please check your input and try again",
status: "error",
title: "Invalid user",
});
return;
}
const addResp = await fetch("/api/gme/add", {
body: JSON.stringify({ user: userToAdd }),
headers: {
"content-type": "application/json",
},
method: "POST",
});
if (!addResp.ok) {
toast({
description: ((await addResp.json()) as { error: string }).error,
status: "error",
title: "Oops",
});
return;
}
toast({
description: `User ${userToAdd} added`,
status: "success",
title: "Success",
});
props.onClose();
}
async function removeUser(user: string) {
const removeResp = await fetch("/api/gme/remove", {
body: JSON.stringify({ user }),
headers: {
"content-type": "application/json",
},
method: "POST",
});
if (!removeResp.ok) {
toast({
description: ((await removeResp.json()) as { error: string }).error,
status: "error",
title: "Oops",
});
} else {
toast({
description: `User ${user} removed`,
status: "success",
title: "Success",
});
setMods(mods.filter((mod: any) => mod.user !== user));
}
}
return (
<Modal isCentered isOpen={props.isOpen} onClose={props.onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Game Moderators</ModalHeader>
<ModalCloseButton />
<ModalBody>
<TableContainer>
<Table variant="simple">
<Thead>
<Tr>
<Th>User</Th>
<Th>Added At</Th>
<Th>Added By</Th>
<Th>Remove</Th>
</Tr>
</Thead>
<Tbody>
{mods.map((mod: any) => (
<Tr>
<Td>{mod.user}</Td>
<Td>{new Date(mod.metadata.time).toLocaleString()}</Td>
<Td>{mod.metadata.user}</Td>
<Td>
<Link onClick={async () => await removeUser(mod.user)}>
Remove
</Link>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<HStack mt="8px">
<Input
maxLength={19}
onChange={(e) => setUserToAdd(e.target.value)}
placeholder="1234567890987654321"
/>
<Button onClick={async () => await addUser()}>Add</Button>
</HStack>
</ModalBody>
</ModalContent>
</Modal>
);
}

View File

@@ -63,13 +63,20 @@ export default function (props: ReportCardProps & { port?: MessagePort }) {
<Heading size="lg">
Report for {props.target_usernames.toString()}
</Heading>
<Text fontSize="xs">ID(s): {props.target_ids.toString()}</Text>
<VStack alignItems="start" gap={0} paddingTop="8px">
<Text fontSize="xs">ID(s): {props.target_ids.toString()}</Text>
<Text fontSize="xs">Report Type: {props.type}</Text>
</VStack>
</CardHeader>
<CardBody>
<div style={{ position: "relative" }}>
<video controls={true} width="100%">
<source src={`/api/uploads/${props.attachments[attachmentIdx]}`} />
<source src="/files/processing.webm" />
<img
alt="User submission evidence"
src={`/api/uploads/${props.attachments[attachmentIdx]}`}
width="100%"
/>
</video>
<HStack
pos="absolute"
@@ -151,6 +158,9 @@ export default function (props: ReportCardProps & { port?: MessagePort }) {
<Radio key={2} value="2">
Ban
</Radio>
<Radio key={3} value="3">
Disallow Server Configurator
</Radio>
</VStack>
</RadioGroup>,
);

View File

@@ -54,11 +54,6 @@
"tag": "3_Row",
"position": "Head of Wall Moderation"
},
{
"id": "688414240209633441",
"tag": "9t_d",
"position": "Events Coordinator"
},
{
"id": "884831305831948338",
"tag": "Mustang",

View File

@@ -43,7 +43,6 @@ export async function onRequestPost(context: RequestContext) {
.bind(context.params.id)
.run();
await fetch(
`https://discord.com/api/v10/guilds/242263977986359297/bans/${appeal.user.id}`,
{
@@ -60,7 +59,7 @@ export async function onRequestPost(context: RequestContext) {
embeds: [
{
title: "Appeal Accepted",
color: 0x00ff00,
color: 65280,
description: `Appeal from user ${appeal.user.username} (${appeal.user.id}) was accepted.`,
fields: [
{

View File

@@ -49,7 +49,7 @@ export async function onRequestPost(context: RequestContext) {
embeds: [
{
title: "Appeal Denied",
color: 0xff0000,
color: 16711680,
description: `Appeal from user ${appeal.user.username} (${appeal.user.id}) was denied.`,
fields: [
{

View File

@@ -74,6 +74,9 @@ export async function onRequestPatch(context: RequestContext) {
const date = new Date();
const currentDay = date.getUTCDate();
// Set to first of month to avoid any issues with month incrementation, then add month and subtract one day to get the last day of this month
date.setUTCDate(1);
date.setUTCMonth(date.getUTCMonth() + 1);
date.setUTCDate(0);
if (
@@ -88,7 +91,7 @@ export async function onRequestPatch(context: RequestContext) {
if (
date.getUTCFullYear() !== eventData.year ||
date.getUTCMonth() !== eventData.month
date.getUTCMonth() + 1 !== eventData.month
)
return jsonError(
"Only events in the current month period can be rescheduled",

View File

@@ -16,29 +16,49 @@ export async function onRequestPost(context: RequestContext) {
if (!appeal) return jsonError("Appeal not found", 400);
const banList = (await getBanList(context)) as {
[k: string]: { BanType: number; Unbanned?: boolean; UnbanReduct?: number };
[k: string]: {
BanType: number;
hidden_from_leaderboards?: boolean;
serverconfigurator_blacklist?: boolean;
Unbanned?: boolean;
UnbanReduct?: number;
};
};
await context.env.D1.prepare("DELETE FROM game_appeals WHERE id = ?;")
.bind(context.params.id)
.run();
if (!banList[appeal.roblox_id])
if (!banList[appeal.roblox_id]?.BanType)
return new Response(null, {
status: 204,
});
banList[appeal.roblox_id] = {
BanType: 0,
Unbanned: true,
UnbanReduct: statsReduction,
};
if (banList[appeal.roblox_id].BanType === 2) {
banList[appeal.roblox_id] = {
BanType: 0,
Unbanned: true,
UnbanReduct: statsReduction,
};
} else if (appeal.type === "server configurator") {
banList[appeal.roblox_id] = {
...banList[appeal.roblox_id],
serverconfigurator_blacklist: false,
};
} else if (appeal.type === "blacklist") {
banList[appeal.roblox_id] = {
...banList[appeal.roblox_id],
hidden_from_leaderboards: false,
Unbanned: true,
UnbanReduct: statsReduction,
};
}
await context.env.D1.prepare(
"INSERT INTO game_mod_logs (action, evidence, executed_at, executor, id, target) VALUES (?, ?, ?, ?, ?, ?);",
)
.bind(
"accept_appeal",
`accept appeal | ${banList[appeal.roblox_id]?.BanType === 2 ? "ban" : appeal.type}`,
`https://carcrushers.cc/mod-queue?id=${context.params.id}&type=gma`,
Date.now(),
context.data.current_user.id,

View File

@@ -3,8 +3,7 @@ import precheck from "./precheck.js";
export async function onRequestPost(context: RequestContext) {
if (
context.request.headers.get("rbx-auth") !==
context.env.ROBLOX_APPEALS_TOKEN
context.request.headers.get("rbx-auth") !== context.env.ROBLOX_APPEALS_TOKEN
)
return jsonError("Unauthorized", 401);
@@ -16,7 +15,7 @@ export async function onRequestPost(context: RequestContext) {
if (precheckData.error) return jsonError(precheckData.error, 500);
const { can_appeal, reason } = precheckData;
const { can_appeal, reason, types } = precheckData;
return jsonResponse(JSON.stringify({ can_appeal, reason }));
return jsonResponse(JSON.stringify({ can_appeal, reason, types }));
}

View File

@@ -3,9 +3,16 @@ import { getBanList } from "../../roblox-open-cloud.js";
export default async function (
context: RequestContext,
user: number,
): Promise<{ can_appeal?: boolean; error?: string; reason?: string }> {
): Promise<{
can_appeal?: boolean;
error?: string;
reason?: string;
types?: string[];
}> {
if (
await context.env.D1.prepare("SELECT * FROM game_appeals WHERE roblox_id = ?;")
await context.env.D1.prepare(
"SELECT * FROM game_appeals WHERE roblox_id = ?;",
)
.bind(user)
.first()
)
@@ -18,7 +25,11 @@ export default async function (
try {
banList = (await getBanList(context)) as {
[k: number]: { BanType: number };
[k: number]: {
BanType: number;
hidden_from_leaderboards?: boolean;
serverconfigurator_blacklist?: boolean;
};
};
} catch {
return {
@@ -42,24 +53,20 @@ export default async function (
).toLocaleString()} to submit another appeal`,
};
let userLogs;
const userLogs = await context.env.D1.prepare(
"SELECT executed_at FROM game_mod_logs WHERE target = ? ORDER BY executed_at DESC;",
)
.bind(user)
.all();
try {
userLogs = await context.env.D1.prepare(
"SELECT executed_at FROM game_mod_logs WHERE target = ? ORDER BY executed_at DESC;",
)
.bind(user)
.all();
if (userLogs.error) throw new Error("Query failed");
} catch {
if (userLogs.error)
return {
error: "Could not determine your eligibility",
};
}
// Legacy bans
if (!userLogs.results.length) return { can_appeal: true, reason: "" };
if (!userLogs.results.length)
return { can_appeal: true, reason: "", types: ["ban"] };
const allowedTime = (userLogs.results[0].executed_at as number) + 2592000000;
@@ -71,5 +78,12 @@ export default async function (
).toLocaleString()} to submit an appeal`,
};
return { can_appeal: true, reason: "" };
const types: string[] = [];
if (banList[user].BanType) types.push("ban");
if (banList[user].hidden_from_leaderboards) types.push("leaderboard");
if (banList[user].serverconfigurator_blacklist)
types.push("server_configurator");
return { can_appeal: true, reason: "", types };
}

View File

@@ -7,11 +7,13 @@ export async function onRequestPost(context: RequestContext) {
if (authHeader !== context.env.ROBLOX_APPEALS_TOKEN)
return jsonError("Unauthorized", 401);
const { id, reasonForUnban, username, whatHappened } = context.data.body;
const { id, reasonForUnban, type, username, whatHappened } =
context.data.body;
if (
typeof id !== "number" ||
typeof reasonForUnban !== "string" ||
!["ban", "leaderboard", "server_configurator"].includes(type) ||
typeof username !== "string" ||
typeof whatHappened !== "string"
)
@@ -43,14 +45,25 @@ export async function onRequestPost(context: RequestContext) {
if (!precheckData.can_appeal)
return jsonError(precheckData.reason as string, 400);
if (!precheckData.types?.includes(type))
return jsonError("Specified punishment type is not applicable", 400);
const appealId = `${id}${
context.request.headers.get("cf-ray")?.split("-")[0]
}${Date.now()}`;
await context.env.D1.prepare(
"INSERT INTO game_appeals (created_at, id, reason_for_unban, roblox_id, roblox_username, what_happened) VALUES (?, ?, ?, ?, ?, ?);",
"INSERT INTO game_appeals (created_at, id, reason_for_unban, roblox_id, roblox_username, type, what_happened) VALUES (?, ?, ?, ?, ?, ?, ?);",
)
.bind(Date.now(), appealId, reasonForUnban, id, username, whatHappened)
.bind(
Date.now(),
appealId,
reasonForUnban,
id,
username,
type,
whatHappened,
)
.run();
await fetch(context.env.REPORTS_WEBHOOK, {

View File

@@ -34,7 +34,11 @@ export async function onRequestGet(context: RequestContext) {
try {
banList = (await getBanList(context)) as {
[k: number]: { BanType: number };
[k: number]: {
BanType: number;
hidden_from_leaderboards?: boolean;
serverconfigurator_blacklist?: boolean;
};
};
} catch (e) {
console.log(e);
@@ -47,8 +51,17 @@ export async function onRequestGet(context: RequestContext) {
const banData = banList[users[0].id];
if (!banData?.BanType) current_status = "Not Moderated";
else if (banData.BanType === 1) current_status = "Hidden from Leaderboards";
else if (banData.BanType === 2) current_status = "Banned";
else if (banData.BanType === 1) {
if (
banData.hidden_from_leaderboards &&
banData.serverconfigurator_blacklist
)
current_status = "Hidden / Server Config Blocked";
else if (banData.hidden_from_leaderboards)
current_status = "Hidden From Leaderboards";
else if (banData.serverconfigurator_blacklist)
current_status = "Server Config Blocked";
} else if (banData.BanType === 2) current_status = "Banned";
const response = {
history: (

View File

@@ -0,0 +1,39 @@
import { jsonError, jsonResponse } from "../../../common.js";
export async function onRequestDelete(context: RequestContext) {
const noteId = context.params.id as string;
const creatorIdResult: null | Record<string, string> =
await context.env.D1.prepare(
"SELECT created_by FROM game_mod_logs WHERE id = ?;",
)
.bind(noteId)
.first();
if (creatorIdResult?.created_by !== context.data.current_user.id)
return jsonError("Cannot delete notes that are not your own", 403);
await context.env.D1.prepare("DELETE FROM game_mod_logs WHERE id = ?;")
.bind(noteId)
.first();
return new Response(null, { status: 204 });
}
export async function onRequestGet(context: RequestContext) {
const noteId = context.params.id as string;
const result = await context.env.D1.prepare(
"SELECT * FROM game_mod_notes WHERE id = ?;",
)
.bind(noteId)
.first();
if (!result) return jsonError("Note not found", 404);
const noteData = structuredClone(result);
const gmeEntry: null | { time: number; user: string; name: string } =
await context.env.DATA.get(`gamemod_${result.created_by}`, "json");
if (gmeEntry) noteData.creator_name = gmeEntry.name;
return jsonResponse(JSON.stringify(noteData));
}

View File

@@ -0,0 +1,8 @@
import { jsonError } from "../../../common.js";
export async function onRequest(context: RequestContext) {
if (!(context.data.current_user?.permissions & (1 << 5)))
return jsonError("Forbidden", 403);
return await context.next();
}

View File

@@ -0,0 +1,23 @@
import { jsonError } from "../../../common.js";
export async function onRequestPost(context: RequestContext) {
const { content, target } = context.data.body;
if (typeof content !== "string")
return jsonError("'content' property is not a string", 400);
if (typeof target !== "number" || !Number.isSafeInteger(target))
return jsonError("'target' property is not a valid number", 400);
if (content.length > 1000)
return jsonError(
"'content' property must be less than 1000 characters",
400,
);
const id = `${Date.now()}${crypto.randomUUID().replaceAll("-", "")}`;
await context.env.D1.prepare(
"INSERT INTO game_mod_notes (content, created_at, created_by, id, target) VALUES (?, ?, ?, ?, ?);",
).bind(content, Date.now(), context.data.current_user.id, id, target).first();
}

View File

@@ -1,10 +1,13 @@
import { jsonError } from "../../common.js";
export async function onRequestPost(context: RequestContext) {
const { user } = context.data.body;
const { user, name } = context.data.body;
if (!user) return jsonError("No user provided", 400);
if (typeof name !== "string" || name.length > 32)
return jsonError("Invalid name", 400);
const existingUser = await context.env.DATA.get(`gamemod_${user}`);
if (existingUser) return jsonError("Cannot add an existing user", 400);
@@ -20,7 +23,12 @@ export async function onRequestPost(context: RequestContext) {
if (!user.match(/^\d{17,19}$/)) return jsonError("Invalid User ID", 400);
const data = { time: Date.now(), user: context.data.current_user.id };
const data = {
created_at: Date.now(),
created_by: context.data.current_user.id,
id: user,
name,
};
await context.env.DATA.put(`gamemod_${user}`, JSON.stringify(data), {
metadata: data,

View File

@@ -1,14 +0,0 @@
import { jsonResponse } from "../../common.js";
export async function onRequestGet(context: RequestContext) {
const list = await context.env.DATA.list({ prefix: "gamemod_" });
const entries = [];
for (const key of list.keys)
entries.push({
metadata: key.metadata,
user: key.name.replace("gamemod_", ""),
});
return jsonResponse(JSON.stringify(entries));
}

View File

@@ -91,8 +91,10 @@ export async function onRequestPost(context: RequestContext) {
requestedNotice.decisions,
);
for (const department of userAdminDepartments)
for (const department of userAdminDepartments) {
if (!JSON.parse(requestedNotice.departments).includes(department)) continue;
decisions[department] = accepted;
}
const applicableDepartments = JSON.parse(requestedNotice.departments).length;

View File

@@ -25,6 +25,11 @@ export async function onRequestGet(context: RequestContext) {
if (!data || data.user?.id !== context.data.current_user.id)
return jsonError("Item does not exist", 404);
if (type === "inactivity") {
data.decisions = JSON.parse(data.decisions);
data.departments = JSON.parse(data.departments);
}
if (type === "report") {
data.attachments = JSON.parse(data.attachments);
data.target_ids = JSON.parse(data.target_ids);

View File

@@ -14,22 +14,30 @@ export async function onRequestPost(context: RequestContext) {
if (!report) return jsonError("Report does not exist", 404);
const actionMap = context.data.body;
const newActions: { [k: string]: { BanType: number } } = {};
const newActions: {
[k: string]: {
BanType: number;
hidden_from_leaderboards?: boolean;
serverconfigurator_blacklist?: boolean;
};
} = {};
const logMap: { [k: string]: number } = {};
const user = JSON.parse(report.user);
for (const [user, action] of Object.entries(actionMap)) {
for (let [user, action] of Object.entries(actionMap)) {
if (
isNaN(parseInt(user)) ||
typeof action !== "number" ||
action < 0 ||
action > 2
action > 3
)
return jsonError("Invalid action map", 400);
if (action === 0) continue;
newActions[user] = { BanType: action };
newActions[user] = {
BanType: action,
};
logMap[user] = action;
}
@@ -41,9 +49,11 @@ export async function onRequestPost(context: RequestContext) {
const actionMap: { [k: number]: string } = {
1: "blacklist",
2: "ban",
3: "server config block",
};
for (const [k, v] of Object.entries(logMap)) {
// If ignore action
if (v === 0) continue;
batchedQueries.push(
@@ -56,12 +66,24 @@ export async function onRequestPost(context: RequestContext) {
parseInt(k),
),
);
// If not a ban action
if (v === 1) {
newActions[k].hidden_from_leaderboards = true;
} else if (v === 3) {
newActions[k].serverconfigurator_blacklist = true;
newActions[k].BanType = 1;
}
}
await context.env.D1.batch(batchedQueries);
const banList = (await getBanList(context)) as {
[k: string]: { BanType: number };
[k: string]: {
BanType: number;
hidden_from_leaderboards?: boolean;
serverconfigurator_blacklist?: boolean;
};
};
await setBanList(context, Object.assign(banList, newActions));

View File

@@ -2,8 +2,14 @@ import { jsonError, jsonResponse } from "../../common.js";
import upload from "../../upload.js";
export async function onRequestPost(context: RequestContext) {
const { description, files, senderTokenId, turnstileResponse, usernames } =
context.data.body;
const {
description,
files,
senderTokenId,
submissionType,
turnstileResponse,
usernames,
} = context.data.body;
if (!context.data.current_user) {
if (typeof turnstileResponse !== "string")
@@ -32,6 +38,9 @@ export async function onRequestPost(context: RequestContext) {
if (!Array.isArray(usernames))
return jsonError("Usernames must be type of array", 400);
if (!["abuse", "exploit"].includes(submissionType))
return jsonError("Invalid submission type", 400);
if (
!["string", "undefined"].includes(typeof description) ||
description?.length > 512
@@ -131,7 +140,19 @@ export async function onRequestPost(context: RequestContext) {
if (
fileParts.length < 2 ||
!["mkv", "mp4", "wmv", "m4v", "gif", "webm"].includes(fileExten)
![
"avif",
"gif",
"jpeg",
"jpg",
"m4v",
"mkv",
"mp4",
"png",
"webm",
"webp",
"wmv",
].includes(fileExten)
)
return jsonError(
`File ${file.name} cannot be uploaded as it is unsupported`,
@@ -162,9 +183,10 @@ export async function onRequestPost(context: RequestContext) {
const uploadUrlResults = await Promise.allSettled(uploadUrlPromises);
const reportId = `${Date.now()}${context.request.headers.get(
"cf-ray",
)}${crypto.randomUUID().replaceAll("-", "")}`;
const reportId = `${Date.now()}${context.request.headers
.get("cf-ray")
?.split("-")
?.at(0)}${crypto.randomUUID().replaceAll("-", "")}`;
const { current_user: currentUser } = context.data;
if (filesToProcess.length)
@@ -196,7 +218,7 @@ export async function onRequestPost(context: RequestContext) {
}
await context.env.D1.prepare(
"INSERT INTO reports (attachments, created_at, id, open, target_ids, target_usernames, user) VALUES (?, ?, ?, 1, ?, ?, ?);",
"INSERT INTO reports (attachments, created_at, id, open, target_ids, target_usernames, type, user) VALUES (?, ?, ?, 1, ?, ?, ?, ?);",
)
.bind(
JSON.stringify(attachments),
@@ -204,6 +226,7 @@ export async function onRequestPost(context: RequestContext) {
reportId,
JSON.stringify(metaIDs),
JSON.stringify(metaNames),
submissionType,
currentUser ? JSON.stringify(currentUser) : null,
)
.run();

View File

@@ -1,12 +1,17 @@
import { AwsClient } from "aws4fetch";
const contentTypes: { [k: string]: string } = {
avif: "image/avif",
gif: "image/gif",
jpeg: "image/jpeg",
jpg: "image/jpeg",
m4v: "video/x-m4v",
mkv: "video/x-matroska",
mov: "video/mp4",
mp4: "video/mp4",
png: "image/png",
webm: "video/webm",
webp: "image/webp",
wmv: "video/x-ms-wmv",
};

4
index.d.ts vendored
View File

@@ -15,7 +15,7 @@ declare global {
event_id: string;
event_type: string;
token: string;
}
};
type RequestContext = EventContext<Env, string, { [k: string]: any }>;
@@ -39,6 +39,7 @@ declare global {
reason_for_unban: string;
roblox_id: number;
roblox_username: string;
type: string;
what_happened: string;
}
@@ -71,6 +72,7 @@ declare global {
open: boolean;
target_ids: number[];
target_usernames: string[];
type: string;
user?: {
id: string;
username: string;

310
package-lock.json generated
View File

@@ -10,28 +10,28 @@
"dependencies": {
"@chakra-ui/react": "^2.10.9",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@fontsource-variable/plus-jakarta-sans": "^5.2.6",
"@remix-run/cloudflare": "^2.16.8",
"@remix-run/cloudflare-pages": "^2.16.8",
"@remix-run/react": "^2.16.8",
"@sentry/react": "^9.27.0",
"@emotion/styled": "^11.14.1",
"@fontsource-variable/plus-jakarta-sans": "^5.2.8",
"@remix-run/cloudflare": "^2.17.1",
"@remix-run/cloudflare-pages": "^2.17.1",
"@remix-run/react": "^2.17.1",
"@sentry/react": "^10.21.0",
"aws4fetch": "^1.0.20",
"dayjs": "^1.11.13",
"framer-motion": "^12.16.0",
"dayjs": "^1.11.18",
"framer-motion": "^12.23.24",
"react": "^18.3.1",
"react-big-calendar": "^1.19.2",
"react-big-calendar": "^1.19.4",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@remix-run/dev": "^2.16.8",
"@types/node": "^22.13.14",
"@types/react": "^18.3.23",
"@types/react-big-calendar": "^1.16.1",
"@remix-run/dev": "^2.17.1",
"@types/node": "^24.9.1",
"@types/react": "^18.3.26",
"@types/react-big-calendar": "^1.16.3",
"@types/react-dom": "^18.3.7",
"dotenv": "^16.5.0",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
"dotenv": "^17.2.3",
"prettier": "^3.6.2",
"typescript": "^5.9.3"
}
},
"node_modules/@ampproject/remapping": {
@@ -649,9 +649,9 @@
}
},
"node_modules/@cloudflare/workers-types": {
"version": "4.20250610.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250610.0.tgz",
"integrity": "sha512-HxnUoey3QxCEfy07pUm7J42jBi9YPHq/hA3fw6JmOqYLHdviHI28OA8lup+2RUaHwDzh6q1DSfrBvvDqde645A==",
"version": "4.20251014.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20251014.0.tgz",
"integrity": "sha512-tEW98J/kOa0TdylIUOrLKRdwkUw0rvvYVlo+Ce0mqRH3c8kSoxLzUH9gfCvwLe0M89z1RkzFovSKAW2Nwtyn3w==",
"license": "MIT OR Apache-2.0",
"peer": true
},
@@ -752,9 +752,9 @@
"license": "MIT"
},
"node_modules/@emotion/styled": {
"version": "11.14.0",
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
"integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
"version": "11.14.1",
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.3",
@@ -1193,9 +1193,9 @@
}
},
"node_modules/@fontsource-variable/plus-jakarta-sans": {
"version": "5.2.6",
"resolved": "https://registry.npmjs.org/@fontsource-variable/plus-jakarta-sans/-/plus-jakarta-sans-5.2.6.tgz",
"integrity": "sha512-w0954jYDlHvzABXY5NgkBtitBiHcULblm1cScowZrRV5V6KtgB17Ubw3knFr82P3qvN48kgQk5DLPhB27OlTnQ==",
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource-variable/plus-jakarta-sans/-/plus-jakarta-sans-5.2.8.tgz",
"integrity": "sha512-iQecBizIdZxezODNHzOn4SvvRMrZL/S8k4MEXGDynCmUrImVW0VmX+tIAMqnADwH4haXlHSXqMgU6+kcfBQJdw==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
@@ -1401,13 +1401,13 @@
}
},
"node_modules/@remix-run/cloudflare": {
"version": "2.16.8",
"resolved": "https://registry.npmjs.org/@remix-run/cloudflare/-/cloudflare-2.16.8.tgz",
"integrity": "sha512-3KBsnmHcZHqUpuVGeYtUVfyGrfHXtV5JOZu8ig6WGeQB2sVMY5zEUNt+tJX2kXaPNswuQjVNk2b+/d6WUbKDOg==",
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/@remix-run/cloudflare/-/cloudflare-2.17.1.tgz",
"integrity": "sha512-JdtThzjMgLiC7Hdg9pO3dX2LRcNdV///fAB8OjAbICfVmR4pgXRffcQLBNH3OSvnk1nwLvseugozUmZTHwbg4Q==",
"license": "MIT",
"dependencies": {
"@cloudflare/kv-asset-handler": "^0.1.3",
"@remix-run/server-runtime": "2.16.8"
"@remix-run/server-runtime": "2.17.1"
},
"engines": {
"node": ">=18.0.0"
@@ -1423,12 +1423,12 @@
}
},
"node_modules/@remix-run/cloudflare-pages": {
"version": "2.16.8",
"resolved": "https://registry.npmjs.org/@remix-run/cloudflare-pages/-/cloudflare-pages-2.16.8.tgz",
"integrity": "sha512-g7REYhAjeTZ7GvlPaMKrDl/lSXrWJOMxQ+tcIMKj0aeT4MBK6vpiqyv84TsOLMrhHYjoqjD393rd8tEESKy/9A==",
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/@remix-run/cloudflare-pages/-/cloudflare-pages-2.17.1.tgz",
"integrity": "sha512-ATJOyO+mu50NzPS74b/7fe6TTgMC+HM3yMd3M5SdqaLu+McPBaldnJrP+WAXNbPCJXDeoFbMneBpfnS1p2TAPQ==",
"license": "MIT",
"dependencies": {
"@remix-run/cloudflare": "2.16.8"
"@remix-run/cloudflare": "2.17.1"
},
"engines": {
"node": ">=18.0.0"
@@ -1444,9 +1444,9 @@
}
},
"node_modules/@remix-run/dev": {
"version": "2.16.8",
"resolved": "https://registry.npmjs.org/@remix-run/dev/-/dev-2.16.8.tgz",
"integrity": "sha512-2EKByaD5CDwh7H56UFVCqc90kCZ9LukPlSwkcsR3gj7WlfL7sXtcIqIopcToAlKAeao3HDbhBlBT2CTOivxZCg==",
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/@remix-run/dev/-/dev-2.17.1.tgz",
"integrity": "sha512-Ou9iIewCs4IIoC5FjYBsfNzcCfdrc+3V8thRjULVMvTDfFxRoL+uNz/AlD3jC7Vm8Q08Iryy0joCOh8oghIhvQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1460,9 +1460,9 @@
"@babel/types": "^7.22.5",
"@mdx-js/mdx": "^2.3.0",
"@npmcli/package-json": "^4.0.1",
"@remix-run/node": "2.16.8",
"@remix-run/node": "2.17.1",
"@remix-run/router": "1.23.0",
"@remix-run/server-runtime": "2.16.8",
"@remix-run/server-runtime": "2.17.1",
"@types/mdx": "^2.0.5",
"@vanilla-extract/integration": "^6.2.0",
"arg": "^5.0.1",
@@ -1514,8 +1514,8 @@
"node": ">=18.0.0"
},
"peerDependencies": {
"@remix-run/react": "^2.16.8",
"@remix-run/serve": "^2.16.8",
"@remix-run/react": "^2.17.0",
"@remix-run/serve": "^2.17.0",
"typescript": "^5.1.0",
"vite": "^5.1.0 || ^6.0.0",
"wrangler": "^3.28.2"
@@ -1588,6 +1588,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/@remix-run/dev/node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/@remix-run/dev/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -1658,13 +1671,13 @@
"license": "MIT"
},
"node_modules/@remix-run/node": {
"version": "2.16.8",
"resolved": "https://registry.npmjs.org/@remix-run/node/-/node-2.16.8.tgz",
"integrity": "sha512-foeYXU3mdaBJZnbtGbM8mNdHowz2+QnVGDRo7P3zgFkmsccMEflArGZNbkACGKd9xwDguTxxMJ6cuXBC4jIfgQ==",
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/@remix-run/node/-/node-2.17.1.tgz",
"integrity": "sha512-pHmHTuLE1Lwazulx3gjrHobgBCsa+Xiq8WUO0ruLeDfEw2DU0c0SNSiyNkugu3rIZautroBwRaOoy7CWJL9xhQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@remix-run/server-runtime": "2.16.8",
"@remix-run/server-runtime": "2.17.1",
"@remix-run/web-fetch": "^4.4.2",
"@web3-storage/multipart-parser": "^1.0.0",
"cookie-signature": "^1.1.0",
@@ -1685,13 +1698,13 @@
}
},
"node_modules/@remix-run/react": {
"version": "2.16.8",
"resolved": "https://registry.npmjs.org/@remix-run/react/-/react-2.16.8.tgz",
"integrity": "sha512-JmoBUnEu/nPLkU6NGNIG7rfLM97gPpr1LYRJeV680hChr0/2UpfQQwcRLtHz03w1Gz1i/xONAAVOvRHVcXkRlA==",
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/@remix-run/react/-/react-2.17.1.tgz",
"integrity": "sha512-5MqRK2Z5gkQMDqGfjXSACf/HzvOA+5ug9kiSqaPpK9NX0OF4NlS+cAPKXQWuzc2iLSp6r1RGu8FU1jpZbhsaug==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.0",
"@remix-run/server-runtime": "2.16.8",
"@remix-run/server-runtime": "2.17.1",
"react-router": "6.30.0",
"react-router-dom": "6.30.0",
"turbo-stream": "2.4.1"
@@ -1720,9 +1733,9 @@
}
},
"node_modules/@remix-run/server-runtime": {
"version": "2.16.8",
"resolved": "https://registry.npmjs.org/@remix-run/server-runtime/-/server-runtime-2.16.8.tgz",
"integrity": "sha512-ZwWOam4GAQTx10t+wK09YuYctd2Koz5Xy/klDgUN3lmTXmwbV0tZU0baiXEqZXrvyD+WDZ4b0ADDW9Df3+dpzA==",
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/@remix-run/server-runtime/-/server-runtime-2.17.1.tgz",
"integrity": "sha512-d1Vp9FxX4KafB111vP2E5C1fmWzPI+gHZ674L1drq+N8Bp9U6FBspi7GAZSU5K5Kxa4T6UF+aE1gK6pVi9R8sw==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.0",
@@ -1746,12 +1759,12 @@
}
},
"node_modules/@remix-run/server-runtime/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
"integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">= 8"
"node": ">= 12"
}
},
"node_modules/@remix-run/web-blob": {
@@ -2052,88 +2065,88 @@
]
},
"node_modules/@sentry-internal/browser-utils": {
"version": "9.27.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.27.0.tgz",
"integrity": "sha512-SJa7f6Ct1BzP8rWEomnshSGN1CmT+axNKvT+StrbFPD6AyHnYfFLJpKgc2iToIJHB/pmeuOI9dUwqtzVx+5nSw==",
"version": "10.21.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.21.0.tgz",
"integrity": "sha512-QRHpCBheLd/88Z2m3ABMriV0MweW+pcGKuVsH61/UdziKcQLdoQpOSvGg0/0CuqFm2UjL7237ZzLdZrWaCOlfQ==",
"license": "MIT",
"dependencies": {
"@sentry/core": "9.27.0"
"@sentry/core": "10.21.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/feedback": {
"version": "9.27.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.27.0.tgz",
"integrity": "sha512-e7L8eG0y63RulN352lmafoCCfQGg4jLVT8YLx6096eWu/YKLkgmVpgi8livsT5WREnH+HB+iFSrejOwK7cRkhw==",
"version": "10.21.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.21.0.tgz",
"integrity": "sha512-6SnRR2FiW6TMwCE0PqbueHkkpeVnjOjz00R+/mX25Dp1U5BU5TzbXHzn9Y4wKnaD3Rzz4+nnzVkpHAOL3SppGw==",
"license": "MIT",
"dependencies": {
"@sentry/core": "9.27.0"
"@sentry/core": "10.21.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay": {
"version": "9.27.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.27.0.tgz",
"integrity": "sha512-n2kO1wOfCG7GxkMAqbYYkpgTqJM5tuVLdp0JuNCqTOLTXWvw6svWGaYKlYpKUgsK9X/GDzJYSXZmfe+Dbg+FJQ==",
"version": "10.21.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.21.0.tgz",
"integrity": "sha512-5tfiKZJzZf9+Xk8SyvoC4ZEVLNmjBZZEaKhVyNo53CLWUWfWOqDc3DB9fj85i/yHFQ0ImdRnaPBc0CIeN00CcA==",
"license": "MIT",
"dependencies": {
"@sentry-internal/browser-utils": "9.27.0",
"@sentry/core": "9.27.0"
"@sentry-internal/browser-utils": "10.21.0",
"@sentry/core": "10.21.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay-canvas": {
"version": "9.27.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.27.0.tgz",
"integrity": "sha512-44rVSt3LCH6qePYRQrl4WUBwnkOk9dzinmnKmuwRksEdDOkVq5KBRhi/IDr7omwSpX8C+KrX5alfKhOx1cP0gQ==",
"version": "10.21.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.21.0.tgz",
"integrity": "sha512-TOLo5mAjJSOuJId8Po44d1hwJ5bIZDtRSoupWpYWqLw1tuUh1tc4vqID11ZXsw9pBzjVIK653BPDX/z/9+Um+Q==",
"license": "MIT",
"dependencies": {
"@sentry-internal/replay": "9.27.0",
"@sentry/core": "9.27.0"
"@sentry-internal/replay": "10.21.0",
"@sentry/core": "10.21.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/browser": {
"version": "9.27.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.27.0.tgz",
"integrity": "sha512-geR3lhRJOmUQqi1WgovLSYcD/f66zYnctdnDEa7j1BW2XIB1nlTJn0mpYyAHghXKkUN/pBpp1Z+Jk0XlVwFYVg==",
"version": "10.21.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.21.0.tgz",
"integrity": "sha512-z/63bUFBQkTfJ5ElhWTYvomz+gZ1GsoH16v4/RGoPY5qZgYxcVO3fkp0opnu3gcbXS0ZW7TLRiHpqhvipDdP6g==",
"license": "MIT",
"dependencies": {
"@sentry-internal/browser-utils": "9.27.0",
"@sentry-internal/feedback": "9.27.0",
"@sentry-internal/replay": "9.27.0",
"@sentry-internal/replay-canvas": "9.27.0",
"@sentry/core": "9.27.0"
"@sentry-internal/browser-utils": "10.21.0",
"@sentry-internal/feedback": "10.21.0",
"@sentry-internal/replay": "10.21.0",
"@sentry-internal/replay-canvas": "10.21.0",
"@sentry/core": "10.21.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/core": {
"version": "9.27.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.27.0.tgz",
"integrity": "sha512-Zb2SSAdWXQjTem+sVWrrAq9L6YYfxyoTwtapaE6C6qZBR5C8Uak0wcYww8StaCFH7dDA/PSW+VxOwjNXocrQHQ==",
"version": "10.21.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.21.0.tgz",
"integrity": "sha512-/+gpOOb2Wr1UbW59WKqNAVVIqFz9FjtUJuPtVh4UanxGCfavMPaKpFzSlaEKJSKDkiCQgANP4O2y8Y5Bh3tvEA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/react": {
"version": "9.27.0",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.27.0.tgz",
"integrity": "sha512-UT7iaGEwTqe06O4mgHfKGTRBHg+U0JSI/id+QxrOji6ksosOsSnSC3Vdq+gPs9pzCCFE+6+DkH6foYNNLIN0lw==",
"version": "10.21.0",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.21.0.tgz",
"integrity": "sha512-BSCGKkepg9QPJRS8AUjtSAFd4lYJLmz3+P+oehViEHQDtRqqmXbVIBLhqwPc05KvRGIl4/kIDjyfDuHCFCJigQ==",
"license": "MIT",
"dependencies": {
"@sentry/browser": "9.27.0",
"@sentry/core": "9.27.0",
"@sentry/browser": "10.21.0",
"@sentry/core": "10.21.0",
"hoist-non-react-statics": "^3.3.2"
},
"engines": {
@@ -2243,13 +2256,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.15.31",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.31.tgz",
"integrity": "sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==",
"version": "24.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
"integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
"undici-types": "~7.16.0"
}
},
"node_modules/@types/parse-json": {
@@ -2265,9 +2278,9 @@
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.23",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
"version": "18.3.26",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz",
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -2275,9 +2288,9 @@
}
},
"node_modules/@types/react-big-calendar": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/@types/react-big-calendar/-/react-big-calendar-1.16.1.tgz",
"integrity": "sha512-pDHFcVWx+BvZbX6U39R4l8c9930vKnfx+09lf4W8r8HuxBDLzGk7Q63ncBmqqnQImEFNDKfwa6MDyu90cfzJ2A==",
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/@types/react-big-calendar/-/react-big-calendar-1.16.3.tgz",
"integrity": "sha512-CR+5BKMhlr/wPgsp+sXOeNKNkoU1h/+6H1XoWuL7xnurvzGRQv/EnM8jPS9yxxBvXI8pjQBaJcI7RTSGiewG/Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2689,9 +2702,9 @@
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3249,9 +3262,9 @@
"license": "MIT"
},
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
"version": "1.11.18",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz",
"integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
"license": "MIT"
},
"node_modules/debug": {
@@ -3405,9 +3418,9 @@
}
},
"node_modules/dotenv": {
"version": "16.5.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
"version": "17.2.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -4101,13 +4114,13 @@
}
},
"node_modules/framer-motion": {
"version": "12.16.0",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.16.0.tgz",
"integrity": "sha512-xryrmD4jSBQrS2IkMdcTmiS4aSKckbS7kLDCuhUn9110SQKG1w3zlq1RTqCblewg+ZYe+m3sdtzQA6cRwo5g8Q==",
"version": "12.23.24",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
"integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.16.0",
"motion-utils": "^12.12.1",
"motion-dom": "^12.23.23",
"motion-utils": "^12.23.6",
"tslib": "^2.4.0"
},
"peerDependencies": {
@@ -4211,6 +4224,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/generator-function": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
"integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/generic-names": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/generic-names/-/generic-names-4.0.0.tgz",
@@ -4812,14 +4835,15 @@
}
},
"node_modules/is-generator-function": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
"integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
"integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.3",
"get-proto": "^1.0.0",
"call-bound": "^1.0.4",
"generator-function": "^2.0.0",
"get-proto": "^1.0.1",
"has-tostringtag": "^1.0.2",
"safe-regex-test": "^1.1.0"
},
@@ -6535,18 +6559,18 @@
}
},
"node_modules/motion-dom": {
"version": "12.16.0",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.16.0.tgz",
"integrity": "sha512-Z2nGwWrrdH4egLEtgYMCEN4V2qQt1qxlKy/uV7w691ztyA41Q5Rbn0KNGbsNVDZr9E8PD2IOQ3hSccRnB6xWzw==",
"version": "12.23.23",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.12.1"
"motion-utils": "^12.23.6"
}
},
"node_modules/motion-utils": {
"version": "12.12.1",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.12.1.tgz",
"integrity": "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==",
"version": "12.23.6",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
"license": "MIT"
},
"node_modules/mri": {
@@ -7389,9 +7413,9 @@
"license": "MIT"
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
@@ -7589,9 +7613,9 @@
}
},
"node_modules/react-big-calendar": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-1.19.2.tgz",
"integrity": "sha512-2orH+TOXPJBlQGwSl9ZnTK2WZR9OfVf0r1s8mnbpjvtENZfmWHP6nXqxmten1vkvzOMqefVGjh5GurM27HHOZw==",
"version": "1.19.4",
"resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-1.19.4.tgz",
"integrity": "sha512-FrvbDx2LF6JAWFD96LU1jjloppC5OgIvMYUYIPzAw5Aq+ArYFPxAjLqXc4DyxfsQDN0TJTMuS/BIbcSB7Pg0YA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.7",
@@ -8848,9 +8872,9 @@
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
@@ -8884,9 +8908,9 @@
}
},
"node_modules/undici": {
"version": "6.21.3",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
"version": "6.22.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz",
"integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -8894,9 +8918,9 @@
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},
@@ -9295,9 +9319,9 @@
}
},
"node_modules/vite": {
"version": "5.4.14",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",
"integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==",
"version": "5.4.19",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
"integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -11,31 +11,31 @@
"dependencies": {
"@chakra-ui/react": "^2.10.9",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@fontsource-variable/plus-jakarta-sans": "^5.2.6",
"@remix-run/cloudflare": "^2.16.8",
"@remix-run/cloudflare-pages": "^2.16.8",
"@remix-run/react": "^2.16.8",
"@sentry/react": "^9.27.0",
"@emotion/styled": "^11.14.1",
"@fontsource-variable/plus-jakarta-sans": "^5.2.8",
"@remix-run/cloudflare": "^2.17.1",
"@remix-run/cloudflare-pages": "^2.17.1",
"@remix-run/react": "^2.17.1",
"@sentry/react": "^10.21.0",
"aws4fetch": "^1.0.20",
"dayjs": "^1.11.13",
"framer-motion": "^12.16.0",
"dayjs": "^1.11.18",
"framer-motion": "^12.23.24",
"react": "^18.3.1",
"react-big-calendar": "^1.19.2",
"react-big-calendar": "^1.19.4",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@remix-run/dev": "^2.16.8",
"@types/node": "^22.13.14",
"@types/react": "^18.3.23",
"@types/react-big-calendar": "^1.16.1",
"@remix-run/dev": "^2.17.1",
"@types/node": "^24.9.1",
"@types/react": "^18.3.26",
"@types/react-big-calendar": "^1.16.3",
"@types/react-dom": "^18.3.7",
"dotenv": "^16.5.0",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
"dotenv": "^17.2.3",
"prettier": "^3.6.2",
"typescript": "^5.9.3"
},
"overrides": {
"@cloudflare/workers-types": "^4.20250610.0"
"@cloudflare/workers-types": "^4.20251014.0"
},
"prettier": {
"endOfLine": "auto"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB