Skip to content

Zoom link #187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions src/app/zoom/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"use server";

if (!process.env.ZOOM_ACCOUNT_ID)
throw new Error("Missing environment variable ZOOM_ACCOUNT_ID in .env");

if (!process.env.ZOOM_CLIENT_ID)
throw new Error("Missing environment variable ZOOM_CLIENT_ID in .env");

if (!process.env.ZOOM_CLIENT_SECRET)
throw new Error("Missing environment variable ZOOM_CLIENT_SECRET in .env");

export async function getZoomAccessToken() {
try {
const response = await fetch("https://zoom.us/oauth/token", {
method: "POST",
headers: {
Authorization: `Basic ${Buffer.from(
`${process.env.ZOOM_CLIENT_ID}:${process.env.ZOOM_CLIENT_SECRET}`,
).toString("base64")}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "account_credentials",
account_id: process.env.ZOOM_ACCOUNT_ID!,
}).toString(),
});

const data = await response.json();

if (!data.access_token) {
throw new Error(`Failed to get access token: ${data.error}`);
}

return data.access_token;
} catch (error) {
throw new Error("Failed to get Zoom access token.");
}
}

export async function createZoomMeeting(
startDateAndTime: string,
presentationDuration: number,
) {
try {
const zoomToken = await getZoomAccessToken();
if (!zoomToken) {
throw new Error("Zoom token is missing.");
}

const response = await fetch("https://api.zoom.us/v2/users/me/meetings", {
method: "POST",
headers: {
Authorization: `Bearer ${zoomToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
topic: "Judging Session",
type: 2,
start_time: startDateAndTime,
duration: presentationDuration,
timezone: "UTC",
settings: {
waiting_room: true,
breakout_room: { enable: true },
join_before_host: false,
mute_upon_entry: true,
approval_type: 0,
auto_recording: "none",
},
}),
});

const data = await response.json();

if (data.code) {
throw new Error(data.message || "Failed to create meeting");
}

return { join_url: data.join_url, meeting_id: data.id };
} catch (error) {
throw new Error("Failed to create Zoom meeting.");
}
}
99 changes: 52 additions & 47 deletions src/components/Dashboard/JudgingInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { generateClient } from "aws-amplify/api";
import Image from "next/image";
import Link from "next/link";

import { type Schema } from "@/amplify/data/resource";
import JudgeIcon from "@/images/dashboard/JudgeIcon.png";
Expand All @@ -14,14 +15,12 @@ const client = generateClient<Schema>();

export default function JudgingInfo() {
const userId = useUser().currentUser.username as string;

const { data: userData } = useQuery({
initialDataUpdatedAt: 0,
queryKey: ["User", userId],
queryFn: async () => {
const { data, errors } = await client.models.User.get({
id: userId,
});

const { data, errors } = await client.models.User.get({ id: userId });
if (errors) {
throw new Error("Error fetching user data");
}
Expand All @@ -30,78 +29,66 @@ export default function JudgingInfo() {
enabled: !!userId,
});

//Fetch team data using the teamId from userData

const teamId = userData?.teamId;
const { data: teamData, isFetching: isFetchingTeamName } = useQuery({
initialDataUpdatedAt: 0,
queryKey: ["Team", teamId],
queryFn: async () => {
if (!teamId) {
throw new Error("Team ID is undefined");
}
const { data, errors } = await client.models.Team.get({
id: teamId,
});

if (errors) {
throw new Error("Error fetching team data");
}
if (!teamId) throw new Error("Team ID is undefined");
const { data, errors } = await client.models.Team.get({ id: teamId });
if (errors) throw new Error("Error fetching team data");
return data;
},
enabled: !!teamId,
});

const teamName = teamData?.name || "Team";

//Fetch team data
const { data: teamRoomData, isFetching: isFetchingTeamRoom } = useQuery({
initialDataUpdatedAt: 0,
queryKey: ["TeamRoom", teamId],
queryFn: async () => {
if (!teamId) {
throw new Error("Team ID is undefined");
}
if (!teamId) throw new Error("Team ID is undefined");
const { data, errors } = await client.models.TeamRoom.list({
filter: {
teamId: { eq: teamId },
},
filter: { teamId: { eq: teamId } },
});

if (errors) {
throw new Error("Error fetching team room data");
}
if (errors) throw new Error("Error fetching team room data");
return data;
},
enabled: !!teamId,
});

const timeSlot = teamRoomData?.[0]?.time
? new Date(teamRoomData[0].time).toLocaleString()
: "Room not assigned";
? new Date(teamRoomData[0].time)
: null;

const zoomLink = teamRoomData?.[0]?.zoomLink || null;

const showZoomLink = (() => {
if (!timeSlot) return false;

const current = new Date();

const fiveMinutesBefore = new Date(timeSlot);
fiveMinutesBefore.setMinutes(timeSlot.getMinutes() - 5);

return current >= fiveMinutesBefore;
})();

//Fetch room Id from Team Room
const roomId = teamRoomData?.[0]?.roomId;
const { data: judgeRoomData, isFetching: isFetchingJudgeData } = useQuery({
queryKey: ["Room", roomId],
queryFn: async () => {
if (!roomId) {
throw new Error("Room ID is undefined");
}
if (!roomId) throw new Error("Room ID is undefined");
const { data, errors } = await client.models.User.list({
filter: {
JUDGE_roomId: { eq: roomId },
},
filter: { JUDGE_roomId: { eq: roomId } },
});

if (errors) {
throw new Error("Error fetching team room data");
}
if (errors) throw new Error("Error fetching team room data");
return data;
},
enabled: !!roomId,
});

//Fetch judges in the same room
const judgeNames =
judgeRoomData
?.map((judge) => `${judge.firstName} ${judge.lastName}`)
Expand All @@ -115,33 +102,51 @@ export default function JudgingInfo() {
src={JudgeIcon}
alt={"Judging Icon"}
/>
<div className="text-start font-medium ">
<div className="text-start font-medium">
{isFetchingTeamName ? "Loading..." : <div>{`${teamName}'s `}</div>}
<div className="">Judging Information</div>
</div>
</div>

<div className="flex flex-col gap-2 p-4 text-start">
<div className="font-medium">Time Slot: </div>

{isFetchingTeamRoom ? (
"Loading..."
) : (
<div className=" text-3xl italic text-neutral-800 xl:text-5xl">
{timeSlot}
<div className="text-3xl italic text-neutral-800 xl:text-5xl">
{timeSlot ? timeSlot.toLocaleString() : "Room not assigned"}
</div>
)}
</div>

<div className="flex flex-col gap-2 p-4 text-start">
<div className="font-medium">Judges: </div>

{isFetchingJudgeData ? (
"Loading..."
) : (
<div className=" text-3xl italic text-neutral-800 xl:text-5xl">
<div className="text-3xl italic text-neutral-800 xl:text-5xl">
{judgeNames}
</div>
)}
</div>

<div className="flex flex-col gap-2 p-4 text-start">
<div className="font-medium">Zoom Link: </div>
{zoomLink && showZoomLink ? (
<Link
href={zoomLink}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 underline"
>
Join Zoom Meeting
</Link>
) : (
<div className="pointer-events-none cursor-default text-gray-500">
Zoom link coming soon
</div>
)}
</div>
</Card>
);
}
46 changes: 26 additions & 20 deletions src/components/admin/Judging/JudgingSchedule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

export default function JudgingSchedule() {
const queryClient = useQueryClient();
const { mutate } = useMutation({
const { mutateAsync: judgingScheduleMutation } = useMutation({
mutationFn: async ({
judgingSessionsPerTeam,
numOfJudgingRooms,
Expand Down Expand Up @@ -72,7 +72,7 @@
},
});

const { data: teamRoomData, isLoading: isLoadingTeamRooms } = useQuery({

Check failure on line 75 in src/components/admin/Judging/JudgingSchedule.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint Scanning

'teamRoomData' is assigned a value but never used
queryKey: ["TeamRoom"],
queryFn: async () => {
const { data, errors } = await client.models.TeamRoom.list();
Expand Down Expand Up @@ -101,7 +101,7 @@
},
});

const { data: teamData, isLoading: isLoadingTeams } = useQuery({

Check failure on line 104 in src/components/admin/Judging/JudgingSchedule.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint Scanning

'teamData' is assigned a value but never used
queryKey: ["Teams"],

queryFn: async () => {
Expand All @@ -115,7 +115,7 @@
},
});

const isLoading =

Check failure on line 118 in src/components/admin/Judging/JudgingSchedule.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint Scanning

'isLoading' is assigned a value but never used
isLoadingRooms || isLoadingJudges || isLoadingTeamRooms || isLoadingTeams;

const judgeRooms =
Expand All @@ -136,27 +136,33 @@
)
: [];

const judgingEvents =
teamRoomData && teamData //make sure conteents of teamRoomData and teamData are mapped first
? teamRoomData.map((teamRoom) => ({
event_id: teamRoom.id,
title:
teamData
?.filter((team) => team.id === teamRoom.teamId)
.map((team) => team.name)
.join(", ") || "No Team Name",
room_id: teamRoom.roomId,
start: new Date(teamRoom.time),
end: new Date(new Date(teamRoom.time).getTime() + 15 * 60 * 1000),
zoomLink: teamRoom.zoomLink,
}))
: [];
// Update all team rooms with the same zoom link (can change this to different zoom links later on)
const { mutate: updateTeamRoomsWithZoomLink } = useMutation({
mutationFn: async (zoomLink: string) => {
const { data, errors } = await client.models.TeamRoom.list();
if (errors) {
throw errors;
}

const updatePromises = data.map((teamRoom) =>
client.models.TeamRoom.update({
id: teamRoom.id,
zoomLink,
}),
);

await Promise.all(updatePromises);

queryClient.invalidateQueries({ queryKey: ["TeamRoom"] });
},
});

return isLoading ? (
<div>Loading schedule...</div> //JudgeTimeline component is only mapped if all isLoading flags are false
) : (
return (
<>
<RoomAssigner judgingScheduleMutation={mutate} />
<RoomAssigner
judgingScheduleMutation={judgingScheduleMutation}
updateTeamRoomsWithZoomLink={updateTeamRoomsWithZoomLink}
/>
<div className="flex justify-center">
<div className="m-4 w-full max-w-[1500px] rounded-md border border-awesomer-purple bg-light-grey p-4 text-lg text-black">
{judgeRooms && judgingEvents ? (
Expand Down
Loading
Loading