void;
-}) {
- const now = currentDate();
- const nowPlusFive = currentDate(5);
-
- const qrTypes = ["Challenge", "Sponsor", "Workshop"];
-
- const [name, setName] = useState("");
- const [category, setCategory] = useState(qrTypes[0]);
- const [points, setPoints] = useState(5);
- const [uses, setUses] = useState(1);
- const [publicised, setPublicised] = useState(false);
- const [startDate, setStartDate] = useState(now);
- const [endDate, setEndDate] = useState(nowPlusFive);
-
- const [error, setError] = useState("");
-
- function currentDate(addMinutes?: number) {
- let now = new Date();
- now.setMinutes(
- now.getMinutes() - now.getTimezoneOffset() + (addMinutes ?? 0)
- );
- return now.toISOString().slice(0, 16);
- }
-
- async function submitForm() {
- if (!name) return setError("Please set a name!");
- try {
- const { data: qr } = await fetchMegateamsApi("/qr_codes", {
- method: "POST",
- body: JSON.stringify({
- name,
- category: category.toLowerCase(),
- points_value: points,
- max_uses: uses,
- publicised,
- start_time: new Date(startDate).toISOString(),
- expiry_time: new Date(endDate).toISOString(),
- state: true,
- }),
- headers: { "Content-Type": "application/json" },
- });
- setName("");
- setCategory(qrTypes[0]);
- setPoints(5);
- setUses(1);
- setPublicised(false);
- setStartDate(now);
- setEndDate(nowPlusFive);
- setError("");
- displayQR(qr.name, qr.redemption_url, qr.category);
- } catch {
- setError("Failed to create QR code!");
- }
- }
-
- return (
-
-
Generate Custom QR
-
setName(e.target.value)}
- />
-
-
-
setPoints(parseInt(e.target.value))}
- />
-
points
-
setUses(parseInt(e.target.value))}
- />
-
uses
-
-
Start time
-
- setStartDate(e.target.value)}
- />
-
-
-
End time
-
- setEndDate(e.target.value)}
- />
-
-
-
-
Publicised:
-
setPublicised(e.target.checked)}
- />
-
-
- {error &&
{error}
}
-
- );
-}
diff --git a/client/src/app/volunteer/(volunteer)/preset.tsx b/client/src/app/volunteer/(volunteer)/preset.tsx
deleted file mode 100644
index 85cf6c05..00000000
--- a/client/src/app/volunteer/(volunteer)/preset.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { fetchMegateamsApi } from "@/lib/api";
-import { ClockIcon, InformationCircleIcon } from "@heroicons/react/24/outline";
-import { useEffect, useState } from "react";
-import useSWR from "swr";
-
-export default function Preset({
- displayQR,
-}: {
- displayQR: (name: string, url: string, category: string) => void;
-}) {
- const { data: { presets } = { presets: {} }, isLoading } =
- useSWR("/qr_codes/presets");
- const [selected, setSelected] = useState
(null);
- const [error, setError] = useState(null);
-
- let presetsList: any[] = Object.entries(presets).map(
- ([id, preset]: [string, any]) => {
- preset.id = id;
- return preset;
- }
- );
- presetsList.sort((a, b) => b.name.localeCompare(a.name));
-
- useEffect(() => {
- if (presetsList.length && !selected) setSelected(presetsList[0]);
- }, [presets]);
-
- function getExpiryDate(minutesValid: number) {
- let now = new Date();
- now.setMinutes(now.getMinutes() + minutesValid);
- return now.toLocaleString("en-GB", {
- timeStyle: "short",
- dateStyle: "short",
- });
- }
-
- async function generateQR() {
- try {
- const { data: qr } = await fetchMegateamsApi(
- "/qr_codes/presets/" + encodeURIComponent(selected.id),
- {
- method: "POST",
- body: JSON.stringify({
- name: selected.name,
- publicised: false,
- }),
- headers: { "Content-Type": "application/json" },
- }
- );
- setError(null);
- displayQR(qr.name, qr.redemption_url, qr.category);
- } catch {
- setError("Failed to generate QR!");
- }
- }
-
- if (isLoading || !selected) return <>>;
-
- return (
-
-
-
{selected.description}
-
-
-
{getExpiryDate(selected.minutesValid)}
-
-
-
-
- {selected.points} point{selected.points !== 1 ? "s" : ""}
- ,
- {selected.uses} use{selected.uses !== 1 ? "s" : ""}
-
-
-
- {error &&
{error}
}
-
- );
-}
diff --git a/client/src/app/volunteer/challenges/page.tsx b/client/src/app/volunteer/challenges/page.tsx
deleted file mode 100644
index e0379032..00000000
--- a/client/src/app/volunteer/challenges/page.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-"use client";
-
-import { useAutoAnimate } from "@formkit/auto-animate/react";
-import { redirect } from "next/navigation";
-import useSWR from "swr";
-import { useFormState } from "react-hooks-use-form-state";
-import { useState } from "react";
-
-import { ButtonModal } from "@/components/button-modal";
-import { fetchMegateamsApi } from "@/lib/api";
-import { isAdmin } from "@/lib/is-role";
-import { useMegateamsContext } from "@/hooks/use-megateams-context";
-
-export default function Challenges() {
- const { user, userIsLoading } = useMegateamsContext();
- const { data = { challenges: [] }, mutate: mutateChallenges } = useSWR<{
- challenges: any[];
- }>("/qr_codes/challenges");
-
- const [animationParent] = useAutoAnimate();
-
- const [challenges, setChallenges, resetForm] = useFormState(data.challenges);
- const [message, setMessage] = useState("");
- const [messageOpen, setMessageOpen] = useState(false);
-
- if (userIsLoading) return <>>;
- if (!user) return redirect("/")
- if (!isAdmin(user)) return redirect("/volunteer")
-
- async function updatePosition(oldPos: number, newPos: number) {
- try {
- let newList = [...challenges];
- if (newPos > oldPos) {
- for (let challenge of newList) {
- if (challenge.rank === oldPos) {
- challenge.rank = newPos;
- } else if (challenge.rank > oldPos && challenge.rank <= newPos) {
- challenge.rank--;
- }
- }
- }
- if (newPos < oldPos) {
- for (let challenge of newList) {
- if (challenge.rank === oldPos) {
- challenge.rank = newPos;
- } else if (challenge.rank >= newPos && challenge.rank < oldPos) {
- challenge.rank++;
- }
- }
- }
- newList.sort((a, b) => a.rank - b.rank);
- setChallenges(newList);
- await fetchMegateamsApi("/qr_codes/challenges/reorder", {
- method: "POST",
- body: JSON.stringify({
- challenges: challenges.map(({ id, rank }) => ({ id, rank })),
- }),
- headers: {
- "Content-Type": "application/json",
- },
- });
- await mutateChallenges();
- } catch {
- setMessage("Failed to reorder challenges!");
- setMessageOpen(true);
- }
- resetForm();
- }
-
- async function unpublicise(id: number) {
- try {
- await fetchMegateamsApi("/qr_codes/" + encodeURIComponent(id), {
- method: "PATCH",
- body: JSON.stringify({ publicised: false }),
- headers: {
- "Content-Type": "application/json",
- },
- });
- await mutateChallenges();
- } catch {
- setMessage("Failed to unpublicise challenge!");
- setMessageOpen(true);
- }
- resetForm();
- }
-
- return (
- <>
-
-
Manage Challenge List
-
- {challenges.map(({ title, points, rank, id }) => (
- -
-
{title}
- {points} points
-
-
Position:
-
-
-
-
- ))}
-
-
- setMessageOpen(bool)}
- content={{message}
}
- itemsClass="items-center"
- buttons={
-
- }
- />
- >
- );
-}
diff --git a/client/src/components/megateams-context-provider.tsx b/client/src/components/guilds-context-provider.tsx
similarity index 63%
rename from client/src/components/megateams-context-provider.tsx
rename to client/src/components/guilds-context-provider.tsx
index a0186da7..89599152 100644
--- a/client/src/components/megateams-context-provider.tsx
+++ b/client/src/components/guilds-context-provider.tsx
@@ -6,39 +6,39 @@ import type { KeyedMutator } from "swr";
import { type User, useUser } from "@/hooks/use-user";
import { type Team, useTeam } from "@/hooks/use-team";
-type MegateamContextProps = {
+type GuildContextProps = {
user: User | null | undefined
- userError: unknown | undefined
mutateUser: KeyedMutator
userIsLoading: boolean
team: Team | null | undefined
- teamError: unknown | undefined
mutateTeam: KeyedMutator
teamIsLoading: boolean
}
-export const MegateamsContextContext = React.createContext(null)
+export const GuildsContextContext = React.createContext(null)
-export function MegateamsContextProvider({ children }: { children?: React.ReactNode }) {
+export function GuildsContextProvider({ children }: { children?: React.ReactNode }) {
const { data: user, error: userError, mutate: mutateUser, isLoading: userIsLoading } = useUser()
const { data: team, error: teamError, mutate: mutateTeam, isLoading: teamIsLoading } = useTeam()
+ // throw the error to the nearest error boundary (error.tsx in app directory)
+ if (userError != null) throw userError
+ if (teamError != null) throw teamError
+
return (
-
{children}
-
+
)
}
diff --git a/client/src/components/leaderboard/mega-chart.tsx b/client/src/components/leaderboard/guild-chart.tsx
similarity index 74%
rename from client/src/components/leaderboard/mega-chart.tsx
rename to client/src/components/leaderboard/guild-chart.tsx
index 10df6335..ded84534 100644
--- a/client/src/components/leaderboard/mega-chart.tsx
+++ b/client/src/components/leaderboard/guild-chart.tsx
@@ -14,7 +14,7 @@ import resolveConfig from "tailwindcss/resolveConfig";
import tailwindConfig from "tailwindcss/defaultConfig";
import useSWR from "swr";
-import { fetchMegateamsApi } from "@/lib/api";
+import { fetchGuildsApi } from "@/lib/api";
ChartJS.register(
CategoryScale,
@@ -25,14 +25,14 @@ ChartJS.register(
);
export default function MegaChart() {
- const { data: { megateams } = { megateams: null } } = useSWR(
- "/megateams",
- fetchMegateamsApi,
+ const { data: { guilds } = { guilds: null } } = useSWR(
+ "/guilds",
+ fetchGuildsApi,
{ refreshInterval: 1000 }
);
- megateams?.sort((a: any, b: any) => {
- return a.megateam_name.localeCompare(b.megateam_name);
+ guilds?.sort((a: any, b: any) => {
+ return a.guild_name.localeCompare(b.guild_name);
});
const isDark = useMediaQuery({
@@ -43,20 +43,27 @@ export default function MegaChart() {
let largestPoints = 0;
- megateams?.forEach((megateam: any) => {
- megateam.image = new Image();
- megateam.image.src = `/${megateam.megateam_name}/icon.png`;
- if (megateam.points > largestPoints) {
- largestPoints = megateam.points;
+ guilds?.forEach((guild: any) => {
+ guild.image = new Image();
+ guild.image.src = `/${guild.guild_name}/icon.png`;
+ if (guild.points > largestPoints) {
+ largestPoints = guild.points;
}
});
+ const guildDisplayNames = new Map([
+ ["Centre of the Earth", "CotE"],
+ ["Atlantis", "Atlantis"],
+ ["Moon", "Moon"],
+ ["Mysterious Island", "MI"],
+ ])
+
const dataset = {
- labels: megateams?.map((team: any) => team.megateam_name),
+ labels: guilds?.map((team: any) => guildDisplayNames.get(team.guild_name)),
datasets: [
{
label: "Points",
- data: megateams?.map((team: any) => team.points),
+ data: guilds?.map((team: any) => team.points),
...(isDark
? {
// @ts-ignore
@@ -94,7 +101,7 @@ export default function MegaChart() {
plugins: {
datalabels,
annotation: {
- annotations: megateams?.map((team: any, i: number) => {
+ annotations: guilds?.map((team: any, i: number) => {
const options: AnnotationOptions = {
type: "box",
yMin: Math.max(largestPoints * 0.6, team.points * 0.75),
diff --git a/client/src/components/leaderboard/leaderboard.tsx b/client/src/components/leaderboard/index.tsx
similarity index 86%
rename from client/src/components/leaderboard/leaderboard.tsx
rename to client/src/components/leaderboard/index.tsx
index eb3eab61..cbd3adfc 100644
--- a/client/src/components/leaderboard/leaderboard.tsx
+++ b/client/src/components/leaderboard/index.tsx
@@ -3,16 +3,16 @@
import * as React from "react";
import useSWR from "swr";
-import { getPositionMedal } from "@/lib/rankEmojis";
+import { getPositionMedal } from "@/lib/rank-emojis";
import TeamName from "@/components/team-name";
-import { fetchMegateamsApi } from "@/lib/api";
+import { fetchGuildsApi } from "@/lib/api";
-import MegaChart from "./mega-chart";
+import MegaChart from "./guild-chart";
export default function Leaderboard() {
const { data: { teams } = { teams: null } } = useSWR(
"/teams",
- fetchMegateamsApi,
+ fetchGuildsApi,
{ refreshInterval: 1000 }
);
@@ -23,7 +23,7 @@ export default function Leaderboard() {
return (
-
Megateams Leaderboard
+
Guilds Leaderboard
diff --git a/client/src/components/tabbed-page.tsx b/client/src/components/tabbed-page.tsx
index d12c8f5f..248f52a6 100644
--- a/client/src/components/tabbed-page.tsx
+++ b/client/src/components/tabbed-page.tsx
@@ -7,8 +7,7 @@ import { Dialog } from "@headlessui/react";
import * as React from "react";
import { SWRConfig } from "swr";
-import { fetchMegateamsApi } from "@/lib/api";
-import { useMegateamsContext } from "@/hooks/use-megateams-context";
+import { fetchGuildsApi } from "@/lib/api";
import { ButtonModal } from "./button-modal";
@@ -34,21 +33,13 @@ export function TabbedPage({
}) {
const path = usePathname();
const [open, setOpen] = React.useState(false);
- const { mutateUser } = useMegateamsContext();
-
- async function signOut() {
- await fetchMegateamsApi("/auth/logout", { method: "POST" });
- await mutateUser(null);
- }
return (
-
+
-
-
DURHACK
+
+
DurHack
@@ -109,13 +100,12 @@ export function TabbedPage({
}
buttons={
<>
-
+