-
Notifications
You must be signed in to change notification settings - Fork 0
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
Zoom link #187
base: main
Are you sure you want to change the base?
Zoom link #187
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,48 @@ | ||||||||||
import { NextResponse } from "next/server"; | ||||||||||
|
||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
export async function GET(req: Request) { | ||||||||||
const { searchParams } = new URL(req.url); | ||||||||||
const code = searchParams.get("code"); | ||||||||||
|
||||||||||
if (!code) { | ||||||||||
return NextResponse.json( | ||||||||||
{ error: "No authorization code" }, | ||||||||||
{ status: 400 }, | ||||||||||
); | ||||||||||
} | ||||||||||
|
||||||||||
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: "authorization_code", | ||||||||||
code, | ||||||||||
redirect_uri: process.env.ZOOM_REDIRECT_URI!, | ||||||||||
}).toString(), | ||||||||||
}); | ||||||||||
|
||||||||||
const data = await response.json(); | ||||||||||
|
||||||||||
if (!data.access_token) { | ||||||||||
return NextResponse.json( | ||||||||||
{ | ||||||||||
error: "Failed to exchange authorization code for access token", | ||||||||||
details: data, | ||||||||||
}, | ||||||||||
{ status: 400 }, | ||||||||||
); | ||||||||||
} | ||||||||||
|
||||||||||
return NextResponse.redirect( | ||||||||||
new URL(`/admin/schedule?zoom_token=${data.access_token}`, req.url), | ||||||||||
); | ||||||||||
} catch (error) { | ||||||||||
return NextResponse.json({ error: "???" }, { status: 500 }); | ||||||||||
} | ||||||||||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Throw errors if missing envars |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { NextResponse } from "next/server"; | ||
|
||
export async function GET() { | ||
const zoomAuthUrl = `https://zoom.us/oauth/authorize?response_type=code&client_id=${process.env.ZOOM_CLIENT_ID}&redirect_uri=${process.env.ZOOM_REDIRECT_URI}`; | ||
|
||
return NextResponse.json({ url: zoomAuthUrl }); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { NextResponse } from "next/server"; | ||
|
||
export async function POST(req: Request) { | ||
const { zoom_token, startDateAndTime, presentationDuration } = | ||
await req.json(); | ||
|
||
if (!zoom_token) { | ||
return NextResponse.json( | ||
{ error: "Error: Zoom token is missing." }, | ||
{ status: 401 }, | ||
); | ||
} | ||
|
||
try { | ||
const response = await fetch("https://api.zoom.us/v2/users/me/meetings", { | ||
method: "POST", | ||
headers: { | ||
Authorization: `Bearer ${zoom_token}`, | ||
"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) { | ||
return NextResponse.json( | ||
{ error: data.message || "Failed to create meeting" }, | ||
{ status: 400 }, | ||
); | ||
} | ||
|
||
return NextResponse.json({ join_url: data.join_url, meeting_id: data.id }); | ||
} catch (err) { | ||
console.error("Error creating meeting:", err); | ||
return NextResponse.json( | ||
{ error: "Failed to create Zoom meeting." }, | ||
{ status: 500 }, | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
|
||
import { generateClient } from "aws-amplify/api"; | ||
import Image from "next/image"; | ||
import { useEffect, useState } from "react"; | ||
|
||
import { type Schema } from "@/amplify/data/resource"; | ||
import JudgeIcon from "@/images/dashboard/JudgeIcon.png"; | ||
|
@@ -14,14 +15,13 @@ const client = generateClient<Schema>(); | |
|
||
export default function JudgingInfo() { | ||
const userId = useUser().currentUser.username as string; | ||
const [showZoomLink, setShowZoomLink] = useState(false); | ||
|
||
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"); | ||
} | ||
|
@@ -30,78 +30,72 @@ 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; | ||
|
||
useEffect(() => { | ||
if (timeSlot) { | ||
const checkTime = () => { | ||
const now = new Date(); | ||
const fiveMinutesBefore = new Date(timeSlot); | ||
fiveMinutesBefore.setMinutes(timeSlot.getMinutes() - 5); | ||
|
||
setShowZoomLink(now >= fiveMinutesBefore); | ||
}; | ||
|
||
checkTime(); | ||
const interval = setInterval(checkTime, 60000); | ||
|
||
return () => clearInterval(interval); | ||
} | ||
}, [timeSlot]); | ||
|
||
Comment on lines
+68
to
84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you are using dervied state to get the zoomLink, so why not use derived state to also compute setShowZoomLink? This way you subtract a useeffect and usestate hook improving performance You can do this because of how tanstack query caches the teamroomdataand will update it based on its query key if anything changes |
||
//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}`) | ||
|
@@ -115,33 +109,49 @@ 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> | ||
{showZoomLink && zoomLink ? ( | ||
<a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to use a tag instead? |
||
href={zoomLink} | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
className="text-blue-500 underline" | ||
> | ||
Join Zoom Meeting | ||
</a> | ||
) : ( | ||
<div className="text-gray-500">Zoom link coming soon</div> | ||
)} | ||
</div> | ||
</Card> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use NextJS server actions instead? There's a lot of boiler plate with using route handlers.
https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations