diff --git a/packages/webapp/src/pages/election/round-video-upload.tsx b/packages/webapp/src/pages/election/round-video-upload.tsx index 48d7c8cc1..70701d7e2 100644 --- a/packages/webapp/src/pages/election/round-video-upload.tsx +++ b/packages/webapp/src/pages/election/round-video-upload.tsx @@ -3,301 +3,279 @@ import { RiVideoUploadLine } from "react-icons/ri"; import dayjs from "dayjs"; import { - SideNavLayout, - isValidDelegate, - onError, - uploadIpfsFileWithTransaction, - uploadToIpfs, - useCurrentElection, - useUALAccount, - useElectionState, - useCurrentMemberElectionVotingData, + SideNavLayout, + isValidDelegate, + onError, + uploadIpfsFileWithTransaction, + uploadToIpfs, + useCurrentElection, + useUALAccount, + useElectionState, + useCurrentMemberElectionVotingData } from "_app"; import { - Container, - Text, - Expander, - Heading, - Loader, - VideoSubmissionFormAndPreview, - VideoSubmissionPhase, + Container, + Text, + Expander, + Heading, + Loader, + VideoSubmissionFormAndPreview, + VideoSubmissionPhase } from "_app/ui"; import { - ElectionStatus, - ErrorLoadingElection, - setElectionRoundVideo, + ElectionStatus, + ErrorLoadingElection, + setElectionRoundVideo } from "elections"; import { RoundHeader } from "elections/components/ongoing-election-components"; import { MemberGateContainer } from "members"; import { MemberNFT } from "nfts/interfaces"; export const RoundVideoUploadPage = () => { - const { - data: currentElection, - isLoading: isLoadingElection, - isError: isErrorElection, - } = useCurrentElection(); - const { - data: electionState, - isLoading: isLoadingElectionState, - isError: isErrorElectionState, - } = useElectionState(); + const { + data: currentElection, + isLoading: isLoadingElection, + isError: isErrorElection + } = useCurrentElection(); + const { + data: electionState, + isLoading: isLoadingElectionState, + isError: isErrorElectionState + } = useElectionState(); - const isLoading = isLoadingElection || isLoadingElectionState; - const isError = isErrorElection || isErrorElectionState; + const isLoading = isLoadingElection || isLoadingElectionState; + const isError = isErrorElection || isErrorElectionState; - if (isLoading) return ; - if (isError || !currentElection) return ; + if (isLoading) return ; + if (isError || !currentElection) return ; - const uploadLimitTime = electionState - ? dayjs(electionState.last_election_time + "Z").add(2, "weeks") - : dayjs().add(1, "day"); + const uploadLimitTime = electionState + ? dayjs(electionState.last_election_time + "Z").add(2, "weeks") + : dayjs().add(1, "day"); - const isUploadExpired = - dayjs().isAfter(uploadLimitTime) && - currentElection?.electionState === ElectionStatus.Registration; + const isUploadExpired = + dayjs().isAfter(uploadLimitTime) && + currentElection?.electionState === ElectionStatus.Registration; - return ( - -
- - Election video upload service - - - {isUploadExpired ? ( -
- Election is complete and upload videos time is - expired. -
- ) : ( - <> - - Election video files are typically large and - require many minutes to upload completely. Do - not close this tab during the upload. A - confirmation will be displayed when your upload - is complete. - - - Be patient, and remember you have 2 weeks from - the beginning of the election to complete your - election video uploads. - - - )} -
- - {!isUploadExpired && ( - - )} - -
-
- ); + return ( + +
+ + Election video upload service + + + {isUploadExpired ? ( +
Election is complete and upload videos time is expired.
+ ) : ( + <> + + Election video files are typically large and require many + minutes to upload completely. Do not close this tab during the + upload. A confirmation will be displayed when your upload is + complete. + + + Be patient, and remember you have 2 weeks from the beginning of + the election to complete your election video uploads. + + + )} +
+ + {!isUploadExpired && ( + + )} + +
+
+ ); }; export default RoundVideoUploadPage; const RoundVideoUploadList = ({ electionState }: { electionState: string }) => { - const [ualAccount] = useUALAccount(); - const { - data: currentMemberElectionVotingData, - isLoading: isLoadingCurrentMemberElectionVotingData, - isError: isErrorCurrentMemberElectionVotingData, - } = useCurrentMemberElectionVotingData(ualAccount?.accountName); - const [videoSubmissionPhase, setVideoSubmissionPhase] = useState< - VideoSubmissionPhase | undefined - >(undefined); - const [uploadCompleteMessage, setUploadCompleteMessage] = useState({ - roundIndex: 0, - message: "", - }); - const [uploadErrorMessage, setUploadErrorMessage] = useState({ - roundIndex: 0, - message: "", - }); + const [ualAccount] = useUALAccount(); + const { + data: currentMemberElectionVotingData, + isLoading: isLoadingCurrentMemberElectionVotingData, + isError: isErrorCurrentMemberElectionVotingData + } = useCurrentMemberElectionVotingData(ualAccount?.accountName); + const [videoSubmissionPhase, setVideoSubmissionPhase] = useState< + VideoSubmissionPhase | undefined + >(undefined); + const [uploadCompleteMessage, setUploadCompleteMessage] = useState({ + roundIndex: 0, + message: "" + }); + const [uploadErrorMessage, setUploadErrorMessage] = useState({ + roundIndex: 0, + message: "" + }); - if (isLoadingCurrentMemberElectionVotingData) { - return ; - } else if (isErrorCurrentMemberElectionVotingData) { - return ; - } + if (isLoadingCurrentMemberElectionVotingData) { + return ; + } else if (isErrorCurrentMemberElectionVotingData) { + return ; + } - if (!currentMemberElectionVotingData?.votes.length) { - return ( - - - It appears you haven't participated in any election round. - Nothing to upload. - - - ); - } + if (!currentMemberElectionVotingData?.votes.length) { + return ( + + + It appears you haven't participated in any election round. Nothing to + upload. + + + ); + } - const resetErrorMessage = () => { - setUploadErrorMessage({ - roundIndex: 0, - message: "", - }); - }; + const resetErrorMessage = () => { + setUploadErrorMessage({ + roundIndex: 0, + message: "" + }); + }; - const submitElectionRoundVideo = (roundIndex: number) => async ( - videoFile: File - ) => { - resetErrorMessage(); - try { - setVideoSubmissionPhase("uploading"); - const videoHash = await uploadToIpfs(videoFile); + const submitElectionRoundVideo = (roundIndex: number) => async ( + videoFile: File + ) => { + resetErrorMessage(); + try { + setVideoSubmissionPhase("uploading"); + const videoHash = await uploadToIpfs(videoFile); - const authorizerAccount = ualAccount.accountName; - const transaction = setElectionRoundVideo( - authorizerAccount, - roundIndex, - videoHash - ); - console.info(transaction); - setVideoSubmissionPhase("signing"); - const signedTrx = await ualAccount.signTransaction(transaction, { - broadcast: false, - expireSeconds: 1 * 60 * 60, // 1 hour (max expiration) - }); - console.info("electvideo trx", signedTrx); + const authorizerAccount = ualAccount.accountName; + const transaction = setElectionRoundVideo( + authorizerAccount, + roundIndex, + videoHash + ); + console.info(transaction); + setVideoSubmissionPhase("signing"); + const signedTrx = await ualAccount.signTransaction(transaction, { + broadcast: false, + expireSeconds: 1 * 60 * 60 // 1 hour (max expiration) + }); + console.info("electvideo trx", signedTrx); - setVideoSubmissionPhase("finishing"); - await uploadIpfsFileWithTransaction( - signedTrx, - videoHash, - videoFile - ); + setVideoSubmissionPhase("finishing"); + await uploadIpfsFileWithTransaction(signedTrx, videoHash, videoFile); - setVideoSubmissionPhase(undefined); - setUploadCompleteMessage({ - roundIndex, - message: "Election video uploaded successfully!", - }); - } catch (error) { - onError(error as Error, "Error uploading election round video"); - setVideoSubmissionPhase(undefined); - setUploadErrorMessage({ - roundIndex, - message: - "There was an error uploading your video. Please try again.", - }); - } - }; + setVideoSubmissionPhase(undefined); + setUploadCompleteMessage({ + roundIndex, + message: "Election video uploaded successfully!" + }); + } catch (error) { + onError(error as Error, "Error uploading election round video"); + setVideoSubmissionPhase(undefined); + setUploadErrorMessage({ + roundIndex, + message: "There was an error uploading your video. Please try again." + }); + } + }; - return ( - <> - {currentMemberElectionVotingData.votes.map((vote) => { - return ( -
- - } - startExpanded - type="inactive" - > - - - } - submitButtonText="Upload meeting video" - title="Upload your election video recording." - subtitle="" - action="electvideo" - uploadErrorMessage={ - uploadErrorMessage.roundIndex === - vote.roundIndex - ? uploadErrorMessage.message - : undefined - } - uploadCompleteMessage={ - uploadCompleteMessage.roundIndex === - vote.roundIndex - ? uploadCompleteMessage.message - : undefined - } - /> - - -
- ); - })} - - ); + return ( + <> + {currentMemberElectionVotingData.votes.map((vote) => { + return ( +
+ + } + startExpanded + type="inactive" + > + + + } + submitButtonText="Upload meeting video" + title="Upload your election video recording." + subtitle="" + action="electvideo" + uploadErrorMessage={ + uploadErrorMessage.roundIndex === vote.roundIndex + ? uploadErrorMessage.message + : undefined + } + uploadCompleteMessage={ + uploadCompleteMessage.roundIndex === vote.roundIndex + ? uploadCompleteMessage.message + : undefined + } + /> + + +
+ ); + })} + + ); }; const LoaderSection = () => ( - - - + + + ); interface HeaderProps { - isOngoing: boolean; - roundIndex: number; - winner?: MemberNFT; - roundStartTime?: dayjs.Dayjs; - roundEndTime?: dayjs.Dayjs; + isOngoing: boolean; + roundIndex: number; + winner?: MemberNFT; + roundStartTime?: dayjs.Dayjs; + roundEndTime?: dayjs.Dayjs; } const Header = ({ - isOngoing, - roundIndex, - winner, - roundStartTime, - roundEndTime, + isOngoing, + roundIndex, + winner, + roundStartTime, + roundEndTime }: HeaderProps) => { - const subText = - winner && isValidDelegate(winner.account) - ? `Delegate: ${winner.name}` - : roundStartTime && roundEndTime - ? `${roundStartTime - .utc() - .format("LT")} - ${roundEndTime.utc().format("LT")} UTC` - : "Consensus not achieved"; + const subText = + winner && isValidDelegate(winner.account) + ? `Delegate: ${winner.name}` + : roundStartTime && roundEndTime + ? `${roundStartTime.utc().format("LT")} - ${roundEndTime + .utc() + .format("LT")} UTC` + : "Consensus not achieved"; - return ( - - Round {roundIndex + 1} {!isOngoing && "completed"} - - } - sublineComponent={ - - {subText} - - } - /> - ); + return ( + + Round {roundIndex + 1} {!isOngoing && "completed"} + + } + sublineComponent={ + + {subText} + + } + /> + ); }; diff --git a/packages/webapp/src/pages/election/stats.tsx b/packages/webapp/src/pages/election/stats.tsx index cb0359fc6..c7bd07b91 100644 --- a/packages/webapp/src/pages/election/stats.tsx +++ b/packages/webapp/src/pages/election/stats.tsx @@ -1,22 +1,22 @@ import React from "react"; import { - RoundBasicQueryData, - RoundGroupQueryData, - RoundWithGroupQueryData, - SideNavLayout, - useCountdown, - useCurrentGlobalElectionData, + RoundBasicQueryData, + RoundGroupQueryData, + RoundWithGroupQueryData, + SideNavLayout, + useCountdown, + useCurrentGlobalElectionData } from "_app"; import { Container, Heading, Loader, Expander, Text } from "_app/ui"; import { - Avatars, - DelegateChip, - ErrorLoadingElection, - RoundStage, - useRoundStageTimes, - VoteData, - VotingMemberChip, + Avatars, + DelegateChip, + ErrorLoadingElection, + RoundStage, + useRoundStageTimes, + VoteData, + VotingMemberChip } from "elections"; import { MembersGrid } from "members"; import { MemberNFT } from "nfts/interfaces"; @@ -24,287 +24,272 @@ import { RoundHeader } from "elections/components/ongoing-election-components"; import { ConsensometerBlocks } from "elections/components/ongoing-election-components/ongoing-round/round-info/consensometer"; export const ElectionStatsPage = () => { - const { - data: globalElectionData, - isLoading: isLoading, - isError: isError, - } = useCurrentGlobalElectionData(); + const { + data: globalElectionData, + isLoading: isLoading, + isError: isError + } = useCurrentGlobalElectionData(); - return ( - -
- - Election - - {isLoading ? ( - - - - ) : isError || !globalElectionData ? ( - - ) : ( - <> - -
- All results - - {globalElectionData.time.format("LL")} - -
- -
- {globalElectionData.rounds.map((round) => ( - - ))} - - )} -
-
- ); + return ( + +
+ + Election + + {isLoading ? ( + + + + ) : isError || !globalElectionData ? ( + + ) : ( + <> + +
+ All results + {globalElectionData.time.format("LL")} +
+ +
+ {globalElectionData.rounds.map((round) => ( + + ))} + + )} +
+
+ ); }; export default ElectionStatsPage; interface RoundSegmentProps { - round: RoundWithGroupQueryData; + round: RoundWithGroupQueryData; } const RoundSegment = ({ round }: RoundSegmentProps) => { - return ( - } - showContentDivider - > -
- {round.groups.map((group, i) => ( - - ))} -
-
- ); + return ( + } showContentDivider> +
+ {round.groups.map((group, i) => ( + + ))} +
+
+ ); }; interface GlobalRoundHeaderProps { - round: RoundBasicQueryData; + round: RoundBasicQueryData; } const GlobalRoundHeader = ({ round }: GlobalRoundHeaderProps) => { - const { stage, currentStageEndTime } = useRoundStageTimes( - round.votingBegin, - round.votingEnd - ); + const { stage, currentStageEndTime } = useRoundStageTimes( + round.votingBegin, + round.votingEnd + ); - const { hmmss } = useCountdown({ endTime: currentStageEndTime.toDate() }); - const roundNum = round.roundIndex + 1; + const { hmmss } = useCountdown({ endTime: currentStageEndTime.toDate() }); + const roundNum = round.roundIndex + 1; - const isChiefDelegateRound = round.numGroups === 1; - const roundTitle = isChiefDelegateRound - ? "Chief Delegates" - : `Round ${roundNum}`; + const isChiefDelegateRound = round.numGroups === 1; + const roundTitle = isChiefDelegateRound + ? "Chief Delegates" + : `Round ${roundNum}`; - const roundStatusLabel = () => { - if (isChiefDelegateRound) { - return round.resultsAvailable ? "" : "elected"; - } else if (round.resultsAvailable) { - return <>completed; - } - switch (stage) { - case RoundStage.PreMeeting: - return ( - <> - starts in: {hmmss} - - ); - case RoundStage.PostMeeting: - return ( - <> - finalizes in:{" "} - {hmmss} - - ); - case RoundStage.Complete: - return <>completed; - default: - return <>in progress; - } - }; + const roundStatusLabel = () => { + if (isChiefDelegateRound) { + return round.resultsAvailable ? "" : "elected"; + } else if (round.resultsAvailable) { + return <>completed; + } + switch (stage) { + case RoundStage.PreMeeting: + return ( + <> + starts in: {hmmss} + + ); + case RoundStage.PostMeeting: + return ( + <> + finalizes in: {hmmss} + + ); + case RoundStage.Complete: + return <>completed; + default: + return <>in progress; + } + }; - const subText = `${round.votingBegin.utc().format("LT")} - ${round.votingEnd.utc().format("LT")} UTC`; + const subText = `${round.votingBegin + .utc() + .format("LT")} - ${round.votingEnd.utc().format("LT")} UTC`; - return ( - - {roundTitle} {roundStatusLabel()} - - } - sublineComponent={ - - {subText} - - } - /> - ); + return ( + + {roundTitle} {roundStatusLabel()} + + } + sublineComponent={ + + {subText} + + } + /> + ); }; interface GroupSegmentProps { - group: RoundGroupQueryData; - groupIndex: number; - isFinished: boolean; - isChiefDelegateGroup?: boolean; + group: RoundGroupQueryData; + groupIndex: number; + isFinished: boolean; + isChiefDelegateGroup?: boolean; } interface GroupMembersStats { - [key: string]: { - receivedVotes: number; - votedFor?: string; - video?: string; - isDelegate: boolean; - }; + [key: string]: { + receivedVotes: number; + votedFor?: string; + video?: string; + isDelegate: boolean; + }; } const GroupSegment = ({ - group, - groupIndex, - isFinished, - isChiefDelegateGroup, + group, + groupIndex, + isFinished, + isChiefDelegateGroup }: GroupSegmentProps) => { - // TODO: revisit this, unfortunately the MembersGrid only accepts MemberData, - // even though we don't need it to display the required summarized member - // chip data - const members: MemberNFT[] = group.votes.map((vote) => vote.voter); + // TODO: revisit this, unfortunately the MembersGrid only accepts MemberData, + // even though we don't need it to display the required summarized member + // chip data + const members: MemberNFT[] = group.votes.map((vote) => vote.voter); - const membersStats = group.votes.reduce((membersVotingMap, vote) => { - membersVotingMap[vote.voter.account] = { - receivedVotes: 0, - votedFor: vote.candidate?.name, - video: vote.video, - isDelegate: group.winner?.account === vote.voter.account, - }; - return membersVotingMap; - }, {} as GroupMembersStats); + const membersStats = group.votes.reduce((membersVotingMap, vote) => { + membersVotingMap[vote.voter.account] = { + receivedVotes: 0, + votedFor: vote.candidate?.name, + video: vote.video, + isDelegate: group.winner?.account === vote.voter.account + }; + return membersVotingMap; + }, {} as GroupMembersStats); - group.votes - .filter((vote) => vote.candidate) - .forEach( - (vote) => (membersStats[vote.candidate!.account].receivedVotes += 1) - ); + group.votes + .filter((vote) => vote.candidate) + .forEach( + (vote) => (membersStats[vote.candidate!.account].receivedVotes += 1) + ); - return isChiefDelegateGroup ? ( - + ) : ( + - ) : ( - - } - members={members} - groupMembersStats={membersStats} - isFinished={isFinished} - /> - ); + } + members={members} + groupMembersStats={membersStats} + isFinished={isFinished} + /> + ); }; interface GroupProps { - members: MemberNFT[]; - isFinished: boolean; - groupMembersStats: GroupMembersStats; - header?: React.ReactNode; + members: MemberNFT[]; + isFinished: boolean; + groupMembersStats: GroupMembersStats; + header?: React.ReactNode; } const RegularGroup = ({ members, groupMembersStats, header }: GroupProps) => { - return ( - - - {(member: MemberNFT) => { - return ( - - ); - }} - - - ); + return ( + + + {(member: MemberNFT) => { + return ( + + ); + }} + + + ); }; const ChiefDelegateGroup = ({ - members, - groupMembersStats, - isFinished, + members, + groupMembersStats, + isFinished }: GroupProps) => { - return ( - - {(member: MemberNFT) => { - const delegateTitle = - isFinished && groupMembersStats[member.account].isDelegate - ? "Head Chief" - : "Chief Delegate"; - return ( - - ); - }} - - ); + return ( + + {(member: MemberNFT) => { + const delegateTitle = + isFinished && groupMembersStats[member.account].isDelegate + ? "Head Chief" + : "Chief Delegate"; + return ( + + ); + }} + + ); }; const GroupHeader = ({ group, groupIndex, isFinished }: GroupSegmentProps) => { - const consensusData = group.votes.map((groupVotes) => ({ - member: groupVotes.voter.account, - round: groupIndex, - index: 0, - candidate: groupVotes.candidate?.account, - })) as VoteData[]; + const consensusData = group.votes.map((groupVotes) => ({ + member: groupVotes.voter.account, + round: groupIndex, + index: 0, + candidate: groupVotes.candidate?.account + })) as VoteData[]; - const groupLabel = - group.winner?.name || (isFinished ? "[no consensus]" : ""); + const groupLabel = group.winner?.name || (isFinished ? "[no consensus]" : ""); - return ( -
- - Group {groupIndex + 1} - -
- {groupLabel} - -
-
- ); + return ( +
+ + Group {groupIndex + 1} + +
+ {groupLabel} + +
+
+ ); };