import { Box, Button, Card, CardBody, CardFooter, Container, Flex, FormControl, FormLabel, Heading, Link, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Stack, StackDivider, Switch, Text, useDisclosure, useToast, VStack, } from "@chakra-ui/react"; import { useLoaderData } from "@remix-run/react"; import { useState } from "react"; import calendarStyles from "react-big-calendar/lib/css/react-big-calendar.css"; import stylesheet from "../styles/events-team.css"; import { type LinksFunction } from "@remix-run/cloudflare"; export const links: LinksFunction = () => { return [ { href: stylesheet, rel: "stylesheet" }, { href: calendarStyles, rel: "stylesheet" }, ]; }; export async function loader({ context }: { context: RequestContext }) { if (!context.data.current_user) throw new Response(null, { status: 401, }); if ( ![1 << 3, 1 << 4, 1 << 12].find( (p) => context.data.current_user.permissions & p, ) ) throw new Response(null, { status: 403, }); const now = new Date(); const monthEventList = await context.env.D1.prepare( "SELECT answer, approved, created_by, day, details, id, month, pending, performed_at, reached_minimum_player_count, type, year FROM events WHERE month = ? AND year = ? ORDER BY day ASC;", ) .bind(now.getUTCMonth() + 1, now.getUTCFullYear()) .all(); if (monthEventList.error) throw new Response(null, { status: 500, }); const membersList = await context.env.D1.prepare( "SELECT id, name FROM et_members WHERE id IN (SELECT created_by FROM events WHERE month = ? AND year = ?);", ) .bind(now.getUTCMonth() + 1, now.getUTCFullYear()) .all(); if (membersList.error) throw new Response(null, { status: 500, }); return { can_approve: Boolean( [1 << 4, 1 << 12].find((p) => context.data.current_user.permissions & p), ), events: monthEventList.results, members: membersList.results as { id: string; name: string }[], }; } export default function () { const { can_approve, events, members, }: { can_approve: boolean; events: { [k: string]: any }[]; members: { id: string; name: string }[]; } = useLoaderData(); const [eventData, setEventData] = useState(events); const { isOpen, onClose, onOpen } = useDisclosure(); const { isOpen: isCompleteOpen, onClose: closeComplete, onOpen: openComplete, } = useDisclosure(); const { isOpen: isAnsweredOpen, onClose: closeAnswered, onOpen: openAnswered, } = useDisclosure(); const toast = useToast(); const [selectedEvent, setSelectedEvent] = useState(""); const [showOld, setShowOld] = useState(false); async function decide(approved: boolean, eventId: string) { const decisionResp = await fetch( `/api/events-team/events/${eventId}/decision`, { body: JSON.stringify({ approved }), headers: { "content-type": "application/json", }, method: "POST", }, ); if (!decisionResp.ok) { let errorMsg = "Unknown error"; try { errorMsg = ((await decisionResp.json()) as { error: string }).error; } catch {} toast({ description: errorMsg, status: "error", title: "Oops!", }); return; } toast({ description: `Event ${approved ? "approved" : "rejected"}`, status: "success", title: "Success", }); const newEventData = eventData; const eventIdx = eventData.findIndex((e) => e.id === eventId); newEventData[eventIdx].approved = approved; newEventData[eventIdx].pending = false; setEventData([...newEventData]); } async function certify(eventId: string) { const certifyResp = await fetch( `/api/events-team/events/${eventId}/certify`, { body: "{}", headers: { "content-type": "application/json", }, method: "POST", }, ); if (!certifyResp.ok) { let errorMsg = "Unknown error"; try { errorMsg = ((await certifyResp.json()) as { error: string }).error; } catch {} toast({ description: errorMsg, status: "error", title: "Failed to certify game night", }); return; } toast({ description: "Game night certified", status: "success", title: "Success", }); const newEventData = eventData; newEventData[ eventData.findIndex((e) => e.id === eventId) ].reached_minimum_player_count = true; setEventData([...newEventData]); setSelectedEvent(""); } async function markAnswered(eventId: string) { const answerResp = await fetch(`/api/events-team/events/${eventId}/solve`, { body: "{}", headers: { "content-type": "application/json", }, method: "POST", }); closeAnswered(); if (!answerResp.ok) { toast({ description: "Failed to mark as solved", status: "error", title: "Oops", }); return; } const newEventData = eventData; newEventData[eventData.findIndex((e) => e.id === eventId)].answered_at = Date.now(); setEventData([...newEventData]); setSelectedEvent(""); } async function markComplete(eventId: string) { const completeResp = await fetch( `/api/events-team/events/${eventId}/complete`, { body: "{}", headers: { "content-type": "application/json", }, method: "POST", }, ); closeComplete(); if (!completeResp.ok) { let msg = "Unknown error"; try { msg = ((await completeResp.json()) as { error: string }).error; } catch {} toast({ description: msg, status: "error", title: "Failed to complete", }); return; } toast({ description: "Event marked as completed", status: "success", title: "Success", }); const newEventData = eventData; // Technically this won't be the same as the time in the db, but that doesn't matter since this is just to hide the button newEventData[eventData.findIndex((e) => e.id === eventId)].performed_at = Date.now(); setEventData([...newEventData]); setSelectedEvent(""); } return ( Certify Game Night By certifying this game night, you confirm that the minimum number of players was met and you were provided proof. Mark as Completed By marking this event as completed, you confirm that the event creator has performed this event Mark as Solved Are you sure you want to mark this riddle as solved? {eventData .map((event) => { if (!showOld && event.day < new Date().getUTCDate()) return; const eventCreatorName = members.find( (member) => member.id === event.created_by, )?.name; const eventColors: { [k: string]: string } = { fotd: "cyan", gamenight: "blue", rotw: "magenta", qotd: "#9900FF", }; return ( } spacing="4"> Date {event.year}-{event.month}-{event.day} Event Type {event.type.toUpperCase()} Event Details {event.details} {event.type === "rotw" ? ( Riddle Answer {event.answer} ) : null} Host {eventCreatorName ? `${eventCreatorName} (${event.created_by})` : event.created_by} {can_approve && event.pending ? ( <> ) : null} {can_approve && !event.pending && !event.performed_at ? ( ) : null} {can_approve && !event.pending && event.approved && event.performed_at && event.type === "rotw" && !event.answered_at ? ( ) : null} {can_approve && event.approved && event.type === "gamenight" && event.performed_at && !event.reached_minimum_player_count ? ( ) : null} Status:{" "} {event.pending ? "Pending" : event.approved ? event.performed_at ? "Completed" : "Approved" : "Denied"} ); }) .filter((e) => e)} Show old events { setShowOld(e.target.checked); setEventData([...eventData]); }} /> Book an Event Historical Events {can_approve ? ( Events Team Member Management ) : null} ); }