From b5803475f5b8243e94ef9b8fc96c06023535c764 Mon Sep 17 00:00:00 2001 From: Lee Richardson Date: Wed, 9 Feb 2022 11:11:28 -0500 Subject: [PATCH] Put Election API Call (#127) * Add spinner, refactor names * Error handling * Set manifest_hash * Refactor out setManifestHash * Fix infinite loop in joint key select step * Set number_of_guardians and quorum * Fix VS Code debugging profile * Fix bug with manifest hash not being sent * Don't display error if there is no error on joint key upload step * Create a hooks folder and move existing hook into it * Converted client retrieval to hooks per PR feedback * Better UI for loading icon * Final tweaks Disable buttons while loading Redirect to election page Remove caption Fix warnings * PR feedback --- .vscode/launch.json | 12 ++ packages/admin-app/src/App.tsx | 2 +- .../src/components/AppBar/AppBar.tsx | 2 +- .../ElectionSetupWizard.tsx | 31 +++-- .../Steps/JointKeySelectStep.tsx | 27 ++-- .../Steps/JointKeyUploadStep.tsx | 2 +- .../Steps/ManifestPreviewStep.tsx | 131 +++++++++++------- packages/admin-app/src/hooks/useClient.ts | 13 ++ .../admin-app/src/{ => hooks}/useToken.ts | 0 packages/admin-app/src/lang/MessageId.ts | 20 +-- packages/admin-app/src/lang/en.json | 2 +- packages/admin-app/src/pages/LoginPage.tsx | 4 +- .../src/routes/AuthenticatedRoutes.tsx | 2 +- packages/admin-app/src/routes/RouteIds.ts | 1 + 14 files changed, 165 insertions(+), 84 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 packages/admin-app/src/hooks/useClient.ts rename packages/admin-app/src/{ => hooks}/useToken.ts (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c5c6358 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:3001", + "webRoot": "${workspaceFolder}" + } + ] +} diff --git a/packages/admin-app/src/App.tsx b/packages/admin-app/src/App.tsx index 72bd52d..a47b880 100644 --- a/packages/admin-app/src/App.tsx +++ b/packages/admin-app/src/App.tsx @@ -3,7 +3,7 @@ import { BrowserRouter as Router } from 'react-router-dom'; import { ThemeProvider, Theme, StyledEngineProvider } from '@mui/material'; import { AuthenticatedLayout } from './layouts'; import { LoginPage } from './pages'; -import useToken from './useToken'; +import useToken from './hooks/useToken'; import AuthenticatedRoutes from './routes/AuthenticatedRoutes'; import UnauthenticatedLayout from './layouts/UnauthenticatedLayout'; diff --git a/packages/admin-app/src/components/AppBar/AppBar.tsx b/packages/admin-app/src/components/AppBar/AppBar.tsx index 454381b..772d63a 100644 --- a/packages/admin-app/src/components/AppBar/AppBar.tsx +++ b/packages/admin-app/src/components/AppBar/AppBar.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import { MessageId } from '../../lang'; -import useToken from '../../useToken'; +import useToken from '../../hooks/useToken'; import { ReactComponent as ElectionGuardLogo } from '../../images/electionguard-logo.svg'; import routeIds from '../../routes/RouteIds'; diff --git a/packages/admin-app/src/components/ElectionSetupWizard/ElectionSetupWizard.tsx b/packages/admin-app/src/components/ElectionSetupWizard/ElectionSetupWizard.tsx index 2bbd2e0..ea5f64b 100644 --- a/packages/admin-app/src/components/ElectionSetupWizard/ElectionSetupWizard.tsx +++ b/packages/admin-app/src/components/ElectionSetupWizard/ElectionSetupWizard.tsx @@ -1,12 +1,12 @@ import { ApiClientFactory, - ClientFactory, SubmitElectionRequest, ValidateManifestRequest, } from '@electionguard/api-client'; import { Box } from '@mui/material'; import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { useV1Client } from '../../hooks/useClient'; import routeIds from '../../routes/RouteIds'; import { createEnumStepper } from '../../utils/EnumStepper'; @@ -43,19 +43,34 @@ export const ElectionSetupWizard: React.FC = () => { setStep(nextStep); }; const handleChanged = (requestFromStep: SubmitElectionRequest) => { - const newRequest = { + const newRequest: SubmitElectionRequest = { ...request, ...requestFromStep, + context: { + ...request.context, + ...requestFromStep.context, + }, }; setRequest(newRequest); }; const handleUploadManifest = (manifestJson: ValidateManifestRequest) => { setManifest(manifestJson); }; + + const setManifestHash = (manifestHash: string): SubmitElectionRequest => { + const requestWithManifestHash = { + ...request, + context: { ...request.context, manifest_hash: manifestHash }, + }; + setRequest(requestWithManifestHash); + return requestWithManifestHash; + }; + + const v1Client = useV1Client(); const handleSubmit = async () => { - const v1Client = ClientFactory.GetV1Client(); - await v1Client.manifestPut(manifest); - // todo: submit data to API + const manifestCreationResult = await v1Client.manifestPut(manifest); + const requestWithManifestHash = setManifestHash(manifestCreationResult.manifest_hash); + await v1Client.electionPut(requestWithManifestHash); setStep(ElectionSetupStep.SetupComplete); }; @@ -77,14 +92,14 @@ export const ElectionSetupWizard: React.FC = () => { {step === ElectionSetupStep.ManifestPreview && ( setStep(ElectionSetupStep.ManifestUpload)} + onSubmit={handleSubmit} + onCancel={() => setStep(ElectionSetupStep.ManifestUpload)} preview={service.getManifestPreview(manifest, request)} /> )} - navigate(routeIds.home)} /> + navigate(routeIds.electionList)} /> ); diff --git a/packages/admin-app/src/components/ElectionSetupWizard/Steps/JointKeySelectStep.tsx b/packages/admin-app/src/components/ElectionSetupWizard/Steps/JointKeySelectStep.tsx index b8187a2..89dbf5a 100644 --- a/packages/admin-app/src/components/ElectionSetupWizard/Steps/JointKeySelectStep.tsx +++ b/packages/admin-app/src/components/ElectionSetupWizard/Steps/JointKeySelectStep.tsx @@ -1,4 +1,4 @@ -import { ClientFactory, KeyCeremony, SubmitElectionRequest } from '@electionguard/api-client'; +import { KeyCeremony, SubmitElectionRequest } from '@electionguard/api-client'; import { Box, Button, @@ -18,6 +18,7 @@ import { QueryClient, QueryClientProvider } from 'react-query'; import { Message, MessageId } from '../../../lang'; import IconHeader from '../../IconHeader'; +import { useCeremonyClient } from '../../../hooks/useClient'; const useStyles = makeStyles((theme) => ({ root: { @@ -59,25 +60,29 @@ const JointKeySelectStep: React.FC = ({ onNext, onChang } }; - const ceremonyClient = ClientFactory.GetCeremonyClient(); + const ceremonyClient = useCeremonyClient(); + useEffect(() => { + const findKeyCeremonies = async () => { + await ceremonyClient.find(0, 100, { filter: {} }).then((response) => { + setKeyCeremonies(response.key_ceremonies); + }); + }; - const getKeyCeremonies = async () => { - await ceremonyClient.find(0, 100, { filter: {} }).then((response) => { - setKeyCeremonies(response.key_ceremonies); - }); - }; + findKeyCeremonies(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const queryClient = new QueryClient(); const classes = useStyles(); - useEffect(() => { - getKeyCeremonies(); - }); - const onNextClick = () => { const submitElectionRequest = { key_name: keyCeremony?.key_name, + context: { + quorum: keyCeremony?.quorum, + number_of_guardians: keyCeremony?.number_of_guardians, + }, } as SubmitElectionRequest; onChanged(submitElectionRequest); onNext(); diff --git a/packages/admin-app/src/components/ElectionSetupWizard/Steps/JointKeyUploadStep.tsx b/packages/admin-app/src/components/ElectionSetupWizard/Steps/JointKeyUploadStep.tsx index bb31631..d522d74 100644 --- a/packages/admin-app/src/components/ElectionSetupWizard/Steps/JointKeyUploadStep.tsx +++ b/packages/admin-app/src/components/ElectionSetupWizard/Steps/JointKeyUploadStep.tsx @@ -86,7 +86,7 @@ const JointKeyUploadStep: React.FC = ({ onNext, onChang Icon={KeyIcon} /> -
{error}
+ {error &&
{error}
} - - - - + )} + + + + ); }; diff --git a/packages/admin-app/src/hooks/useClient.ts b/packages/admin-app/src/hooks/useClient.ts new file mode 100644 index 0000000..d4509cf --- /dev/null +++ b/packages/admin-app/src/hooks/useClient.ts @@ -0,0 +1,13 @@ +import { AuthClient, CeremonyClient, ClientFactory, V1Client } from '@electionguard/api-client'; + +export function useV1Client(): V1Client { + return ClientFactory.GetV1Client(); +} + +export function useCeremonyClient(): CeremonyClient { + return ClientFactory.GetCeremonyClient(); +} + +export function useAuthClient(): AuthClient { + return ClientFactory.GetAuthClient(); +} diff --git a/packages/admin-app/src/useToken.ts b/packages/admin-app/src/hooks/useToken.ts similarity index 100% rename from packages/admin-app/src/useToken.ts rename to packages/admin-app/src/hooks/useToken.ts diff --git a/packages/admin-app/src/lang/MessageId.ts b/packages/admin-app/src/lang/MessageId.ts index b3dcf3e..1d12feb 100644 --- a/packages/admin-app/src/lang/MessageId.ts +++ b/packages/admin-app/src/lang/MessageId.ts @@ -37,16 +37,16 @@ enum MessageId { ElectionSetup_JointKeySelect_Next = 'election_setup.joint_key_select.next', ElectionSetup_ManifestPreview_Id = 'election_setup.manifest_preview.property.id', - ElectionSetupManifestPreviewTitle = 'election_setup.manifest_preview.title', - ElectionSetupManifestPreviewPropertyName = 'election_setup.manifest_preview.property.name', - ElectionSetupManifestPreviewPropertyNumberOfContests = 'election_setup.manifest_preview.property.number_of_contests', - ElectionSetupManifestPreviewPropertyNumberOfStyles = 'election_setup.manifest_preview.property.numberOfStyles', - ElectionSetupManifestPreviewPropertyStartDate = 'election_setup.manifest_preview.property.start_date', - ElectionSetupManifestPreviewPropertyEndDate = 'election_setup.manifest_preview.property.end_date', - ElectionSetupManifestPreviewPropertyFileName = 'election_setup.manifest_preview.property.file_name', - ElectionSetupManifestPreviewCaption = 'election_setup.manifest_preview.caption', - ElectionSetupManifestPreviewNext = 'election_setup.manifest_preview.next', - ElectionSetupManifestPreviewBackToMenu = 'election_setup.manifest_preview.back_to_menu', + ElectionSetup_ManifestPreview_Title = 'election_setup.manifest_preview.title', + ElectionSetup_ManifestPreview_PropertyName = 'election_setup.manifest_preview.property.name', + ElectionSetup_ManifestPreview_PropertyNumberOfContests = 'election_setup.manifest_preview.property.number_of_contests', + ElectionSetup_ManifestPreview_PropertyNumberOfStyles = 'election_setup.manifest_preview.property.numberOfStyles', + ElectionSetup_ManifestPreview_PropertyStartDate = 'election_setup.manifest_preview.property.start_date', + ElectionSetup_ManifestPreview_PropertyEndDate = 'election_setup.manifest_preview.property.end_date', + ElectionSetup_ManifestPreview_PropertyFileName = 'election_setup.manifest_preview.property.file_name', + ElectionSetup_ManifestPreview_Next = 'election_setup.manifest_preview.next', + ElectionSetup_ManifestPreview_BackToMenu = 'election_setup.manifest_preview.back_to_menu', + ElectionSetup_ManifestPreview_SubmitError = 'election_setup.manifest_preview.submit_error', ElectionSetupSetupCompleteTitle = 'election_setup.setup_complete.title', ElectionSetupSetupCompleteNext = 'election_setup.setup_complete.next', diff --git a/packages/admin-app/src/lang/en.json b/packages/admin-app/src/lang/en.json index d5ce6af..6c724eb 100644 --- a/packages/admin-app/src/lang/en.json +++ b/packages/admin-app/src/lang/en.json @@ -38,9 +38,9 @@ "election_setup.manifest_preview.property.start_date": "Start Date", "election_setup.manifest_preview.property.end_date": "End Date", "election_setup.manifest_preview.property.file_name": "File Name", - "election_setup.manifest_preview.caption": "Preview of Manifest", "election_setup.manifest_preview.next": "Submit", "election_setup.manifest_preview.back_to_menu": "Cancel", + "election_setup.manifest_preview.submit_error": "An error occurred while attempting to save the election", "election_setup.upload_manifest.title": "Upload Election Manifest", "election_setup.upload_manifest.upload": "Select Files to Upload", diff --git a/packages/admin-app/src/pages/LoginPage.tsx b/packages/admin-app/src/pages/LoginPage.tsx index ae7e8b4..c265576 100644 --- a/packages/admin-app/src/pages/LoginPage.tsx +++ b/packages/admin-app/src/pages/LoginPage.tsx @@ -1,13 +1,13 @@ import { Body_login_for_access_token_api_v1_auth_login_post, ErrorMessage, - ClientFactory, Token, } from '@electionguard/api-client'; import { Button, Container, InputAdornment, TextField } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import { AccountCircle, Lock } from '@mui/icons-material'; import React, { useState } from 'react'; +import { useAuthClient } from '../hooks/useClient'; const useStyles = makeStyles((theme) => ({ root: { @@ -44,9 +44,9 @@ export const LoginPage: React.FC = ({ setToken }) => { const [password, setPassword] = useState(''); const [result, setResult] = useState(); + const authClient = useAuthClient(); const handleSubmit: React.FormEventHandler = async (e) => { e.preventDefault(); - const authClient = ClientFactory.GetAuthClient(); const loginParams = { username, password, diff --git a/packages/admin-app/src/routes/AuthenticatedRoutes.tsx b/packages/admin-app/src/routes/AuthenticatedRoutes.tsx index d137f94..27d0e3d 100644 --- a/packages/admin-app/src/routes/AuthenticatedRoutes.tsx +++ b/packages/admin-app/src/routes/AuthenticatedRoutes.tsx @@ -26,7 +26,7 @@ const AuthenticatedRoutes: React.FC = () => ( } /> } /> - } /> + } /> } /> } /> } /> diff --git a/packages/admin-app/src/routes/RouteIds.ts b/packages/admin-app/src/routes/RouteIds.ts index 971995f..f29d217 100644 --- a/packages/admin-app/src/routes/RouteIds.ts +++ b/packages/admin-app/src/routes/RouteIds.ts @@ -1,5 +1,6 @@ const routeIds = { home: '/', + electionList: '/election', }; export default routeIds;