Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
car-crushers-portal/app/routes/et-members_.strikes_.$uid.tsx
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
242 lines (219 sloc)
5.73 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Button, | |
Container, | |
Heading, | |
Link, | |
Modal, | |
ModalBody, | |
ModalCloseButton, | |
ModalContent, | |
ModalFooter, | |
ModalHeader, | |
ModalOverlay, | |
Table, | |
TableContainer, | |
Tbody, | |
Td, | |
Text, | |
Textarea, | |
Th, | |
Thead, | |
Tr, | |
useDisclosure, | |
useToast, | |
} from "@chakra-ui/react"; | |
import { LoaderFunctionArgs } from "@remix-run/cloudflare"; | |
import { useLoaderData } from "@remix-run/react"; | |
import { useState } from "react"; | |
export async function loader({ | |
context, | |
params, | |
}: { | |
context: RequestContext; | |
params: LoaderFunctionArgs & { uid: string }; | |
}) { | |
const { current_user: user } = context.data; | |
if (!user) | |
throw new Response(null, { | |
status: 401, | |
}); | |
if (![1 << 3, 1 << 4, 1 << 12].find((p) => user.permissions & p)) | |
throw new Response(null, { | |
status: 403, | |
}); | |
const strikeData = await context.env.D1.prepare( | |
"SELECT * FROM et_strikes WHERE user = ?;", | |
) | |
.bind(params.uid) | |
.all(); | |
return { | |
can_manage: Boolean([1 << 4, 1 << 12].find((p) => user.permissions & p)), | |
strikes: strikeData.results, | |
user: params.uid, | |
}; | |
} | |
export default function () { | |
const { can_manage, strikes, user } = useLoaderData<typeof loader>(); | |
const [strikeData, setStrikeData] = useState(strikes); | |
const toast = useToast(); | |
const [rmStrikeId, setRmStrikeId] = useState(""); | |
const [strikeReason, setStrikeReason] = useState(""); | |
async function removeStrike(id: string) { | |
const removeResp = await fetch(`/api/events-team/strikes/${id}`, { | |
method: "DELETE", | |
}); | |
if (!removeResp.ok) { | |
let msg = "Unknown error"; | |
try { | |
msg = ((await removeResp.json()) as { error: string }).error; | |
} catch {} | |
toast({ | |
description: msg, | |
status: "error", | |
title: "Failed to remove strike", | |
}); | |
return; | |
} | |
toast({ | |
description: `Strike ${id} was removed`, | |
status: "success", | |
title: "Strike Removed", | |
}); | |
setStrikeData(strikeData.filter((strike) => strike.id !== id)); | |
closeRmStrike(); | |
} | |
async function addStrike() { | |
const addStrikeResp = await fetch("/api/events-team/strikes/new", { | |
body: JSON.stringify({ | |
reason: strikeReason, | |
user, | |
}), | |
headers: { | |
"content-type": "application/json", | |
}, | |
method: "POST", | |
}); | |
if (!addStrikeResp.ok) { | |
let msg = "Unknown error"; | |
try { | |
msg = ((await addStrikeResp.json()) as { error: string }).error; | |
} catch {} | |
toast({ | |
description: msg, | |
status: "error", | |
title: "Failed to add strike", | |
}); | |
return; | |
} | |
toast({ | |
description: "Strike added", | |
status: "success", | |
title: "Success", | |
}); | |
const newStrikeData = strikeData; | |
newStrikeData.push(await addStrikeResp.json()); | |
setStrikeData(newStrikeData); | |
closeAddStrike(); | |
} | |
const { | |
isOpen: rmStrikeOpen, | |
onClose: closeRmStrike, | |
onOpen: openRmStrike, | |
} = useDisclosure(); | |
const { | |
isOpen: addStrikeOpen, | |
onClose: closeAddStrike, | |
onOpen: openAddStrike, | |
} = useDisclosure(); | |
return ( | |
<Container maxW="container.lg"> | |
<Modal isOpen={rmStrikeOpen} onClose={closeRmStrike}> | |
<ModalOverlay /> | |
<ModalContent> | |
<ModalHeader>Remove Strike</ModalHeader> | |
<ModalCloseButton /> | |
<ModalBody> | |
<Text>Are you sure you want to remove this strike?</Text> | |
</ModalBody> | |
<ModalFooter> | |
<Button mr="8px">No</Button> | |
<Button | |
colorScheme="red" | |
onClick={async () => await removeStrike(rmStrikeId)} | |
> | |
Yes | |
</Button> | |
</ModalFooter> | |
</ModalContent> | |
</Modal> | |
<Modal isOpen={addStrikeOpen} onClose={closeAddStrike}> | |
<ModalOverlay /> | |
<ModalContent> | |
<ModalHeader>Add Strike</ModalHeader> | |
<ModalCloseButton /> | |
<ModalBody> | |
<Heading mb="8px" size="xs"> | |
Reason | |
</Heading> | |
<Textarea | |
onChange={(e) => setStrikeReason(e.target.value)} | |
placeholder="Strike reason" | |
value={strikeReason} | |
/> | |
</ModalBody> | |
<ModalFooter> | |
<Button | |
mr="8px" | |
onClick={() => { | |
closeAddStrike(); | |
setStrikeReason(""); | |
}} | |
> | |
Cancel | |
</Button> | |
<Button colorScheme="red" onClick={async () => await addStrike()}> | |
Add Strike | |
</Button> | |
</ModalFooter> | |
</ModalContent> | |
</Modal> | |
<Heading my="16px">Strikes</Heading> | |
<TableContainer> | |
<Table variant="simple"> | |
<Thead> | |
<Tr> | |
<Th>Time Added</Th> | |
<Th>Added By</Th> | |
<Th>Reason</Th> | |
<Th>Remove</Th> | |
</Tr> | |
</Thead> | |
<Tbody> | |
{strikeData.map((strike: { [k: string]: any }) => ( | |
<Tr> | |
<Td>{new Date(strike.created_at).toUTCString()}</Td> | |
<Td>{strike.created_by}</Td> | |
<Td>{strike.reason}</Td> | |
<Td> | |
{can_manage ? ( | |
<Link | |
onClick={() => { | |
setRmStrikeId(strike.id); | |
openRmStrike(); | |
}} | |
> | |
Remove | |
</Link> | |
) : null} | |
</Td> | |
</Tr> | |
))} | |
</Tbody> | |
</Table> | |
</TableContainer> | |
<Link color="#646cff" onClick={openAddStrike} py="16px"> | |
Add Strike | |
</Link> | |
</Container> | |
); | |
} |