From 36547d6ef6f7e806a6a01216b6b2e6a6ccd5ffbd Mon Sep 17 00:00:00 2001 From: Sam Der Date: Wed, 24 Jan 2024 02:24:41 -0800 Subject: [PATCH 1/9] fix: get_hackers function call --- apps/api/src/routers/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/routers/admin.py b/apps/api/src/routers/admin.py index 85473238..e8b81be5 100644 --- a/apps/api/src/routers/admin.py +++ b/apps/api/src/routers/admin.py @@ -249,7 +249,7 @@ async def waitlist_release(uid: str) -> None: async def participants() -> list[Participant]: """Get list of participants.""" # TODO: non-hackers - return await participant_manager.get_attending_applicants() + return await participant_manager.get_hackers() @router.post("/checkin/{uid}") From af8ae07d5a3a2c48ad618a72932f7eb0606c27b0 Mon Sep 17 00:00:00 2001 From: Sam Der Date: Wed, 24 Jan 2024 02:25:06 -0800 Subject: [PATCH 2/9] Waitlist Promotion System - Create new promote action that shows modal with confirmation button and instructions to participant - Show promote action instead of check in for waitlisted participants - Disable promote action for organizers who are not check-in leads, showing PopOver message - Disable check in action for participants with `WAIVER_SIGNED` status, showing PopOver message --- .../app/admin/participants/Participants.tsx | 38 ++++++++++-- .../participants/components/CheckInModal.tsx | 2 +- .../components/ParticipantAction.tsx | 58 ++++++++++++++++++- .../components/ParticipantsTable.tsx | 5 +- .../components/WaitlistPromotionModal.tsx | 50 ++++++++++++++++ apps/site/src/lib/admin/useParticipants.ts | 9 ++- 6 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 apps/site/src/app/admin/participants/components/WaitlistPromotionModal.tsx diff --git a/apps/site/src/app/admin/participants/Participants.tsx b/apps/site/src/app/admin/participants/Participants.tsx index 087cb239..de27404d 100644 --- a/apps/site/src/app/admin/participants/Participants.tsx +++ b/apps/site/src/app/admin/participants/Participants.tsx @@ -6,19 +6,39 @@ import useParticipants, { Participant } from "@/lib/admin/useParticipants"; import CheckInModal from "./components/CheckInModal"; import ParticipantsTable from "./components/ParticipantsTable"; +import WaitlistPromotionModal from "./components/WaitlistPromotionModal"; function Participants() { - const { participants, loading, checkInParticipant } = useParticipants(); - const [currentParticipant, setCurrentParticipant] = + const { + participants, + loading, + checkInParticipant, + releaseParticipantFromWaitlist, + } = useParticipants(); + const [checkinParticipant, setCheckinParticipant] = + useState(null); + const [promoteParticipant, setPromoteParticipant] = useState(null); const initiateCheckIn = (participant: Participant): void => { - setCurrentParticipant(participant); + setCheckinParticipant(participant); }; const sendCheckIn = async (participant: Participant): Promise => { await checkInParticipant(participant); - setCurrentParticipant(null); + setCheckinParticipant(null); + // TODO: Flashbar notification + }; + + const initiatePromotion = (participant: Participant): void => { + setPromoteParticipant(participant); + }; + + const sendWaitlistPromote = async ( + participant: Participant, + ): Promise => { + await releaseParticipantFromWaitlist(participant); + setCheckinParticipant(null); // TODO: Flashbar notification }; @@ -28,11 +48,17 @@ function Participants() { participants={participants} loading={loading} initiateCheckIn={initiateCheckIn} + initiatePromotion={initiatePromotion} /> setCurrentParticipant(null)} + onDismiss={() => setCheckinParticipant(null)} onConfirm={sendCheckIn} - participant={currentParticipant} + participant={checkinParticipant} + /> + setPromoteParticipant(null)} + onConfirm={sendWaitlistPromote} + participant={promoteParticipant} /> {/* TODO: walk-in promotion modal */} diff --git a/apps/site/src/app/admin/participants/components/CheckInModal.tsx b/apps/site/src/app/admin/participants/components/CheckInModal.tsx index f88c013a..8899636b 100644 --- a/apps/site/src/app/admin/participants/components/CheckInModal.tsx +++ b/apps/site/src/app/admin/participants/components/CheckInModal.tsx @@ -6,7 +6,7 @@ import TextContent from "@cloudscape-design/components/text-content"; import { Participant } from "@/lib/admin/useParticipants"; -interface ActionModalProps { +export interface ActionModalProps { onDismiss: () => void; onConfirm: (participant: Participant) => void; participant: Participant | null; diff --git a/apps/site/src/app/admin/participants/components/ParticipantAction.tsx b/apps/site/src/app/admin/participants/components/ParticipantAction.tsx index 87af7ea9..df8b08e8 100644 --- a/apps/site/src/app/admin/participants/components/ParticipantAction.tsx +++ b/apps/site/src/app/admin/participants/components/ParticipantAction.tsx @@ -1,26 +1,78 @@ +import { useContext } from "react"; + import Button from "@cloudscape-design/components/button"; +import Popover from "@cloudscape-design/components/popover"; -import { Participant } from "@/lib/admin/useParticipants"; +import { Decision, PostAcceptedStatus } from "@/lib/admin/useApplicant"; +import { Participant, Role } from "@/lib/admin/useParticipants"; +import UserContext from "@/lib/admin/UserContext"; interface ParticipantActionProps { participant: Participant; initiateCheckIn: (participant: Participant) => void; + initiatePromotion: (participant: Participant) => void; } function ParticipantAction({ participant, initiateCheckIn, + initiatePromotion, }: ParticipantActionProps) { - // TODO: waitlist promotion + const { role } = useContext(UserContext); + + const isNotCheckinLead = role !== Role.CheckInLead; + const isWaiverSigned = participant.status === PostAcceptedStatus.signed; - return ( + const promoteButton = ( + + ); + + const checkinButton = ( ); + + if (participant.status === Decision.waitlisted) { + if (isNotCheckinLead) { + return ( + + {promoteButton} + + ); + } + return promoteButton; + } else if (isWaiverSigned) { + return ( + + {checkinButton} + + ); + } + return checkinButton; } export default ParticipantAction; diff --git a/apps/site/src/app/admin/participants/components/ParticipantsTable.tsx b/apps/site/src/app/admin/participants/components/ParticipantsTable.tsx index f7169db6..4e7e55a1 100644 --- a/apps/site/src/app/admin/participants/components/ParticipantsTable.tsx +++ b/apps/site/src/app/admin/participants/components/ParticipantsTable.tsx @@ -16,12 +16,14 @@ interface ParticipantsTableProps { participants: Participant[]; loading: boolean; initiateCheckIn: (participant: Participant) => void; + initiatePromotion: (participant: Participant) => void; } function ParticipantsTable({ participants, loading, initiateCheckIn, + initiatePromotion, }: ParticipantsTableProps) { // TODO: sorting // TODO: search functionality @@ -32,9 +34,10 @@ function ParticipantsTable({ ), - [initiateCheckIn], + [initiateCheckIn, initiatePromotion], ); const emptyMessage = ( diff --git a/apps/site/src/app/admin/participants/components/WaitlistPromotionModal.tsx b/apps/site/src/app/admin/participants/components/WaitlistPromotionModal.tsx new file mode 100644 index 00000000..ca6ae2a7 --- /dev/null +++ b/apps/site/src/app/admin/participants/components/WaitlistPromotionModal.tsx @@ -0,0 +1,50 @@ +import Box from "@cloudscape-design/components/box"; +import Button from "@cloudscape-design/components/button"; +import Modal from "@cloudscape-design/components/modal"; +import SpaceBetween from "@cloudscape-design/components/space-between"; +import TextContent from "@cloudscape-design/components/text-content"; + +import { ActionModalProps } from "./CheckInModal"; + +function WaitlistPromotionModal({ + onDismiss, + onConfirm, + participant, +}: ActionModalProps) { + if (participant === null) { + return ; + } + + return ( + + + + + + + } + header={`Promote ${participant?.first_name} ${participant?.last_name} Off Waitlist`} + > + + +
    + {/* TODO: actual instructions for check-in leads */} +
  • Log into the portal
  • +
  • Sign waiver
  • +
  • Confirm attendance
  • +
+
+ {/* TODO: badge barcode input */} +
+
+ ); +} +export default WaitlistPromotionModal; diff --git a/apps/site/src/lib/admin/useParticipants.ts b/apps/site/src/lib/admin/useParticipants.ts index f5a76410..5562795d 100644 --- a/apps/site/src/lib/admin/useParticipants.ts +++ b/apps/site/src/lib/admin/useParticipants.ts @@ -3,7 +3,7 @@ import useSWR from "swr"; import { Status, Uid } from "@/lib/admin/useApplicant"; -const enum Role { +export const enum Role { Director = "director", Organizer = "organizer", CheckInLead = "checkin_lead", @@ -40,11 +40,18 @@ function useParticipants() { await axios.post(`/api/admin/checkin/${participant._id}`); }; + const releaseParticipantFromWaitlist = async (participant: Participant) => { + console.log(`Promoted to waitlist`, participant); + // TODO: implement mutation for showing checked in on each day + await axios.post(`/api/admin/waitlist-release/${participant._id}`); + }; + return { participants: data ?? [], loading: isLoading, error, checkInParticipant, + releaseParticipantFromWaitlist, }; } From d7c9cd80d03cf9b78b0fe0e3339178a76237ecf5 Mon Sep 17 00:00:00 2001 From: Sam Der Date: Wed, 24 Jan 2024 10:04:59 -0800 Subject: [PATCH 3/9] Update apps/site/src/app/admin/participants/Participants.tsx Co-authored-by: Taesung Hwang <44419552+taesungh@users.noreply.github.com> --- apps/site/src/app/admin/participants/Participants.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/site/src/app/admin/participants/Participants.tsx b/apps/site/src/app/admin/participants/Participants.tsx index de27404d..9f7617f9 100644 --- a/apps/site/src/app/admin/participants/Participants.tsx +++ b/apps/site/src/app/admin/participants/Participants.tsx @@ -38,7 +38,7 @@ function Participants() { participant: Participant, ): Promise => { await releaseParticipantFromWaitlist(participant); - setCheckinParticipant(null); + setPromoteParticipant(null); // TODO: Flashbar notification }; From 97688246d4ea293e271094747cd8a545fe8b9cae Mon Sep 17 00:00:00 2001 From: Sam Der Date: Wed, 24 Jan 2024 10:17:15 -0800 Subject: [PATCH 4/9] grant directors promotion, along with refactoring --- .../components/ParticipantAction.tsx | 34 +++++++------------ .../components/ParticipantActionPopver.tsx | 26 ++++++++++++++ apps/site/src/lib/admin/authorization.ts | 5 +++ 3 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 apps/site/src/app/admin/participants/components/ParticipantActionPopver.tsx diff --git a/apps/site/src/app/admin/participants/components/ParticipantAction.tsx b/apps/site/src/app/admin/participants/components/ParticipantAction.tsx index df8b08e8..04185c60 100644 --- a/apps/site/src/app/admin/participants/components/ParticipantAction.tsx +++ b/apps/site/src/app/admin/participants/components/ParticipantAction.tsx @@ -1,17 +1,19 @@ import { useContext } from "react"; import Button from "@cloudscape-design/components/button"; -import Popover from "@cloudscape-design/components/popover"; -import { Decision, PostAcceptedStatus } from "@/lib/admin/useApplicant"; -import { Participant, Role } from "@/lib/admin/useParticipants"; import UserContext from "@/lib/admin/UserContext"; +import { isCheckinLead } from "@/lib/admin/authorization"; +import { Decision, PostAcceptedStatus } from "@/lib/admin/useApplicant"; +import { Participant } from "@/lib/admin/useParticipants"; +import ParticipantActionPopover from "./ParticipantActionPopver"; interface ParticipantActionProps { participant: Participant; initiateCheckIn: (participant: Participant) => void; initiatePromotion: (participant: Participant) => void; } + function ParticipantAction({ participant, initiateCheckIn, @@ -19,7 +21,7 @@ function ParticipantAction({ }: ParticipantActionProps) { const { role } = useContext(UserContext); - const isNotCheckinLead = role !== Role.CheckInLead; + const isCheckin = isCheckinLead(role); const isWaiverSigned = participant.status === PostAcceptedStatus.signed; const promoteButton = ( @@ -27,7 +29,7 @@ function ParticipantAction({ variant="inline-link" ariaLabel={`Promote ${participant._id} off waitlist`} onClick={() => initiatePromotion(participant)} - disabled={isNotCheckinLead} + disabled={isCheckin} > Promote @@ -45,31 +47,19 @@ function ParticipantAction({ ); if (participant.status === Decision.waitlisted) { - if (isNotCheckinLead) { + if (isCheckin) { return ( - + {promoteButton} - + ); } return promoteButton; } else if (isWaiverSigned) { return ( - + {checkinButton} - + ); } return checkinButton; diff --git a/apps/site/src/app/admin/participants/components/ParticipantActionPopver.tsx b/apps/site/src/app/admin/participants/components/ParticipantActionPopver.tsx new file mode 100644 index 00000000..f66c0c05 --- /dev/null +++ b/apps/site/src/app/admin/participants/components/ParticipantActionPopver.tsx @@ -0,0 +1,26 @@ +import Popover from "@cloudscape-design/components/popover"; +import { PropsWithChildren, ReactNode } from "react"; + +interface ParticipantActionPopoverProps { + content: string; + children?: ReactNode; +} + +function ParticipantActionPopover({ + content, + children, +}: PropsWithChildren) { + return ( + + {children} + + ); +} + +export default ParticipantActionPopover; diff --git a/apps/site/src/lib/admin/authorization.ts b/apps/site/src/lib/admin/authorization.ts index 32e325ca..aff6bf25 100644 --- a/apps/site/src/lib/admin/authorization.ts +++ b/apps/site/src/lib/admin/authorization.ts @@ -1,4 +1,5 @@ const ADMIN_ROLES = ["director", "reviewer", "checkin_lead"]; +const CHECKIN_ROLES = ["director", "checkin_lead"]; const ORGANIZER_ROLES = ["organizer"]; export function isApplicationManager(role: string | null) { @@ -11,3 +12,7 @@ export function isAdminRole(role: string | null) { (ADMIN_ROLES.includes(role) || ORGANIZER_ROLES.includes(role)) ); } + +export function isCheckinLead(role: string | null) { + return role !== null && CHECKIN_ROLES.includes(role); +} From b2179a08df7ee4778323b7adaa6ba7ddbbbc73b9 Mon Sep 17 00:00:00 2001 From: Sam Der Date: Wed, 24 Jan 2024 10:04:59 -0800 Subject: [PATCH 5/9] Update apps/site/src/app/admin/participants/Participants.tsx Co-authored-by: Taesung Hwang <44419552+taesungh@users.noreply.github.com> --- apps/site/src/app/admin/participants/Participants.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/site/src/app/admin/participants/Participants.tsx b/apps/site/src/app/admin/participants/Participants.tsx index de27404d..9f7617f9 100644 --- a/apps/site/src/app/admin/participants/Participants.tsx +++ b/apps/site/src/app/admin/participants/Participants.tsx @@ -38,7 +38,7 @@ function Participants() { participant: Participant, ): Promise => { await releaseParticipantFromWaitlist(participant); - setCheckinParticipant(null); + setPromoteParticipant(null); // TODO: Flashbar notification }; From bd4635ca088a42d380c34b5c7b89492a4bab3927 Mon Sep 17 00:00:00 2001 From: Sam Der Date: Wed, 24 Jan 2024 10:27:18 -0800 Subject: [PATCH 6/9] fix: remove export, PropsWithChildren usage --- .../admin/participants/components/ParticipantActionPopver.tsx | 3 +-- apps/site/src/lib/admin/useParticipants.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/site/src/app/admin/participants/components/ParticipantActionPopver.tsx b/apps/site/src/app/admin/participants/components/ParticipantActionPopver.tsx index f66c0c05..8b704310 100644 --- a/apps/site/src/app/admin/participants/components/ParticipantActionPopver.tsx +++ b/apps/site/src/app/admin/participants/components/ParticipantActionPopver.tsx @@ -1,9 +1,8 @@ import Popover from "@cloudscape-design/components/popover"; -import { PropsWithChildren, ReactNode } from "react"; +import { PropsWithChildren } from "react"; interface ParticipantActionPopoverProps { content: string; - children?: ReactNode; } function ParticipantActionPopover({ diff --git a/apps/site/src/lib/admin/useParticipants.ts b/apps/site/src/lib/admin/useParticipants.ts index 5562795d..7c0e2537 100644 --- a/apps/site/src/lib/admin/useParticipants.ts +++ b/apps/site/src/lib/admin/useParticipants.ts @@ -3,7 +3,7 @@ import useSWR from "swr"; import { Status, Uid } from "@/lib/admin/useApplicant"; -export const enum Role { +const enum Role { Director = "director", Organizer = "organizer", CheckInLead = "checkin_lead", From b5e3de8a98f9ddb9616a0cb8ce0234c01c217ac3 Mon Sep 17 00:00:00 2001 From: Sam Der Date: Wed, 24 Jan 2024 10:28:34 -0800 Subject: [PATCH 7/9] fix: typo in filename --- .../{ParticipantActionPopver.tsx => ParticipantActionPopover.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/site/src/app/admin/participants/components/{ParticipantActionPopver.tsx => ParticipantActionPopover.tsx} (100%) diff --git a/apps/site/src/app/admin/participants/components/ParticipantActionPopver.tsx b/apps/site/src/app/admin/participants/components/ParticipantActionPopover.tsx similarity index 100% rename from apps/site/src/app/admin/participants/components/ParticipantActionPopver.tsx rename to apps/site/src/app/admin/participants/components/ParticipantActionPopover.tsx From afa8185ffb9d2d9cee8f95ed61ec7a3ac7cdc1c9 Mon Sep 17 00:00:00 2001 From: Sam Der Date: Wed, 24 Jan 2024 10:28:54 -0800 Subject: [PATCH 8/9] fix: rename import, isCheckin logic --- .../app/admin/participants/components/ParticipantAction.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/site/src/app/admin/participants/components/ParticipantAction.tsx b/apps/site/src/app/admin/participants/components/ParticipantAction.tsx index 04185c60..663ff00c 100644 --- a/apps/site/src/app/admin/participants/components/ParticipantAction.tsx +++ b/apps/site/src/app/admin/participants/components/ParticipantAction.tsx @@ -6,7 +6,7 @@ import UserContext from "@/lib/admin/UserContext"; import { isCheckinLead } from "@/lib/admin/authorization"; import { Decision, PostAcceptedStatus } from "@/lib/admin/useApplicant"; import { Participant } from "@/lib/admin/useParticipants"; -import ParticipantActionPopover from "./ParticipantActionPopver"; +import ParticipantActionPopover from "./ParticipantActionPopover"; interface ParticipantActionProps { participant: Participant; @@ -29,7 +29,7 @@ function ParticipantAction({ variant="inline-link" ariaLabel={`Promote ${participant._id} off waitlist`} onClick={() => initiatePromotion(participant)} - disabled={isCheckin} + disabled={!isCheckin} > Promote @@ -47,7 +47,7 @@ function ParticipantAction({ ); if (participant.status === Decision.waitlisted) { - if (isCheckin) { + if (!isCheckin) { return ( {promoteButton} From 3bc0aa267c430b5e18ee91e147b3411df5f7e2a0 Mon Sep 17 00:00:00 2001 From: Sam Der Date: Wed, 24 Jan 2024 11:56:57 -0800 Subject: [PATCH 9/9] fix: disable check in button for ACCEPTED status --- .../components/ParticipantAction.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/site/src/app/admin/participants/components/ParticipantAction.tsx b/apps/site/src/app/admin/participants/components/ParticipantAction.tsx index 663ff00c..ce2afedd 100644 --- a/apps/site/src/app/admin/participants/components/ParticipantAction.tsx +++ b/apps/site/src/app/admin/participants/components/ParticipantAction.tsx @@ -4,7 +4,7 @@ import Button from "@cloudscape-design/components/button"; import UserContext from "@/lib/admin/UserContext"; import { isCheckinLead } from "@/lib/admin/authorization"; -import { Decision, PostAcceptedStatus } from "@/lib/admin/useApplicant"; +import { Status } from "@/lib/admin/useApplicant"; import { Participant } from "@/lib/admin/useParticipants"; import ParticipantActionPopover from "./ParticipantActionPopover"; @@ -22,7 +22,8 @@ function ParticipantAction({ const { role } = useContext(UserContext); const isCheckin = isCheckinLead(role); - const isWaiverSigned = participant.status === PostAcceptedStatus.signed; + const isWaiverSigned = participant.status === Status.signed; + const isAccepted = participant.status === Status.accepted; const promoteButton = ( ); - if (participant.status === Decision.waitlisted) { + if (participant.status === Status.waitlisted) { if (!isCheckin) { return ( @@ -55,9 +56,12 @@ function ParticipantAction({ ); } return promoteButton; - } else if (isWaiverSigned) { + } else if (isWaiverSigned || isAccepted) { + const content = isWaiverSigned + ? "Must confirm attendance in portal first" + : "Must sign waiver and confirm attendance in portal"; return ( - + {checkinButton} );