From 737068d5070b3c20ea1cc53b16fe787b8e763ee0 Mon Sep 17 00:00:00 2001 From: Renu Date: Wed, 15 Mar 2023 19:13:23 +0530 Subject: [PATCH 01/13] Add confirmation dialog when replacing a current Election --- admin-ui/pages/elections/[id]/set-current.tsx | 133 ++++++++++++++++++ admin-ui/src/component/ElectionCard.tsx | 10 +- db-json/create-ballots-table.json | 29 ++++ db-json/create-precincts-table.json | 29 ++++ lib/Election.js | 2 + 5 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 admin-ui/pages/elections/[id]/set-current.tsx create mode 100644 db-json/create-ballots-table.json create mode 100644 db-json/create-precincts-table.json diff --git a/admin-ui/pages/elections/[id]/set-current.tsx b/admin-ui/pages/elections/[id]/set-current.tsx new file mode 100644 index 0000000..6412d1f --- /dev/null +++ b/admin-ui/pages/elections/[id]/set-current.tsx @@ -0,0 +1,133 @@ +import { + Alert, + Button, + Grid, + Slider, + SliderThumb, + Typography, +} from "@mui/material"; +import LoggedInLayout from "layout/LoggedInLayout"; +import type { NextPage } from "next"; +import { useRouter } from "next/router"; +import { ReactNode, useEffect, useState } from "react"; + +import NavigateNextIcon from "@mui/icons-material/NavigateNext"; +import { Election, Maybe } from "types"; +import { + setCurrentElection, + getElection +} from "requests/election"; +import Loading from "component/Loading"; +import useCurrentElection from "hooks/useCurrentElection"; + +interface ThumbProps { + children: ReactNode; + [x: string]: any; +} + +function ThumbComponent(props: ThumbProps) { + const { children, ...other } = props; + + return ( + + {children} + + + ); +} + +const CurrentElection: NextPage = () => { + const router = useRouter(); + const { query } = router; + const { id } = query; + const [alertText, setAlertText] = useState(""); + const [currentElection, reloadCurrentElection] = useCurrentElection(); + + const setAlert = (text: string) => { + setAlertText(text); + setTimeout(() => setAlertText(""), 4000); + }; + + const electionId = Array.isArray(id) ? id[0] : id; + + const [election, setElection] = useState>(null); + + useEffect(() => { + const loadElection = async () => { + if (electionId) { + const resp = await getElection(electionId); + setElection(resp); + } + }; + if (electionId) { + loadElection(); + } + }, [electionId]); + + const runSetCurrentElection = async () => { + if (electionId) { + try { + await setCurrentElection(electionId); + router.push("/dashboard"); + } catch (e: any) { + console.error(e); + setAlert(e?.data?.error_description); + } + } + }; + + const archiveMessage = currentElection ? `This action will archive the election for ${currentElection?.electionJurisdictionName} ${currentElection.electionName}.` : ''; + + return ( + + {!election && } + {election && ( + <> + Please confirm to continue. + + Please confirm that you want to set the{" "} + {election?.electionJurisdictionName} {election?.electionName} as the current election. + {archiveMessage} + + + + + + +   + + + { + if (newValue === 100) { + runSetCurrentElection(); + } + }} + components={{ + Thumb: ThumbComponent, + }} + step={null} + marks={[ + { + value: 0, + label: "", + }, + { + value: 100, + label: "", + }, + ]} + defaultValue={0} + /> + + + + {alertText && {alertText}} + + + )} + + ); +}; + +export default CurrentElection; diff --git a/admin-ui/src/component/ElectionCard.tsx b/admin-ui/src/component/ElectionCard.tsx index 1237746..7288d61 100644 --- a/admin-ui/src/component/ElectionCard.tsx +++ b/admin-ui/src/component/ElectionCard.tsx @@ -183,9 +183,13 @@ export default function ElectionCard({ currentElection?.electionStatus == ElectionStatus.open } onClick={async () => { - await setCurrentElection(election.electionId); - if (onUpdateElection) { - onUpdateElection(); + if (currentElection) { + router.push(`/elections/${election.electionId}/set-current`); + } else { + await setCurrentElection(election.electionId); + if (onUpdateElection) { + onUpdateElection(); + } } }} > diff --git a/db-json/create-ballots-table.json b/db-json/create-ballots-table.json new file mode 100644 index 0000000..5281f1e --- /dev/null +++ b/db-json/create-ballots-table.json @@ -0,0 +1,29 @@ +{ + "TableName": "abc_election_ballots_local", + "KeySchema": [{ + "AttributeName": "electionId_ballotId", + "KeyType": "HASH" + }], + "AttributeDefinitions": [{ + "AttributeName": "electionId_ballotId", + "AttributeType": "S" + }, + { + "AttributeName": "electionId", + "AttributeType": "S" + } + ], + "GlobalSecondaryIndexes": [ + { + "IndexName": "electionId-index", + "Projection": { + "ProjectionType": "ALL" + }, + "KeySchema": [{ + "KeyType": "HASH", + "AttributeName": "electionId" + }] + } + ], + "BillingMode": "PAY_PER_REQUEST" +} \ No newline at end of file diff --git a/db-json/create-precincts-table.json b/db-json/create-precincts-table.json new file mode 100644 index 0000000..c00c8c6 --- /dev/null +++ b/db-json/create-precincts-table.json @@ -0,0 +1,29 @@ +{ + "TableName": "abc_election_precincts_local", + "KeySchema": [{ + "AttributeName": "electionId_precinctId", + "KeyType": "HASH" + }], + "AttributeDefinitions": [{ + "AttributeName": "electionId_precinctId", + "AttributeType": "S" + }, + { + "AttributeName": "electionId", + "AttributeType": "S" + } + ], + "GlobalSecondaryIndexes": [ + { + "IndexName": "electionId-index", + "Projection": { + "ProjectionType": "ALL" + }, + "KeySchema": [{ + "KeyType": "HASH", + "AttributeName": "electionId" + }] + } + ], + "BillingMode": "PAY_PER_REQUEST" +} \ No newline at end of file diff --git a/lib/Election.js b/lib/Election.js index 4c151c4..5edbb2c 100644 --- a/lib/Election.js +++ b/lib/Election.js @@ -30,6 +30,8 @@ let documentBase; if (process.env.AWS_SAM_LOCAL) { tableName = `abc_elections_local`; voterTableName = "abc_voters_local"; + ballotsTableName = "abc_election_ballots_local"; + precinctsTableName = "abc_election_precincts_local"; // Allow local dev override //documentBucket = ""; //uploadBucket = ""; From cf8401503c571315adc08d58aa5a41d319ec4ecc Mon Sep 17 00:00:00 2001 From: Renu Date: Wed, 15 Mar 2023 19:17:14 +0530 Subject: [PATCH 02/13] update confirmation message --- admin-ui/pages/elections/[id]/set-current.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin-ui/pages/elections/[id]/set-current.tsx b/admin-ui/pages/elections/[id]/set-current.tsx index 6412d1f..a978f1b 100644 --- a/admin-ui/pages/elections/[id]/set-current.tsx +++ b/admin-ui/pages/elections/[id]/set-current.tsx @@ -85,7 +85,7 @@ const CurrentElection: NextPage = () => { <> Please confirm to continue. - Please confirm that you want to set the{" "} + Please confirm that you want to set the election for{" "} {election?.electionJurisdictionName} {election?.electionName} as the current election. {archiveMessage} From c0d8876af6a62650ad6b02fcfb7681fef158af95 Mon Sep 17 00:00:00 2001 From: Renu Date: Fri, 17 Mar 2023 23:08:04 +0530 Subject: [PATCH 03/13] Add confirmation dialog on new election page, add loaders on button --- admin-ui/src/component/ConfirmationDialog.tsx | 62 ++++++++++ admin-ui/src/component/ElectionForm.tsx | 109 +++++++++++++----- admin-ui/src/component/LoadingButton.tsx | 42 +++++++ admin-ui/src/hooks/useCurrentElection.ts | 1 + examples/edf-validation/.DS_Store | Bin 6148 -> 6148 bytes local-env-osx.json | 4 +- template.yaml | 3 +- 7 files changed, 188 insertions(+), 33 deletions(-) create mode 100644 admin-ui/src/component/ConfirmationDialog.tsx create mode 100644 admin-ui/src/component/LoadingButton.tsx diff --git a/admin-ui/src/component/ConfirmationDialog.tsx b/admin-ui/src/component/ConfirmationDialog.tsx new file mode 100644 index 0000000..eb039dc --- /dev/null +++ b/admin-ui/src/component/ConfirmationDialog.tsx @@ -0,0 +1,62 @@ +import React, { ReactNode, useState } from "react"; +import Button from "@mui/material/Button"; +import Dialog from "@mui/material/Dialog"; +import DialogActions from "@mui/material/DialogActions"; +import DialogContent from "@mui/material/DialogContent"; +import DialogContentText from "@mui/material/DialogContentText"; +import DialogTitle from "@mui/material/DialogTitle"; +import { styled } from "@mui/material/styles"; +import LoadingButton from "./LoadingButton"; + + +interface ConfirmationDialogProps { + title?: string; + open: boolean; + children: ReactNode; + btnCancelText?: string; + btnConfirmText?: string; + onClose: (confirmed: boolean) => void; +} + +export default function ConfirmationDialog({ + title, + children, + open, + btnCancelText = 'Cancel', + btnConfirmText = 'Confirm', + onClose, +}: ConfirmationDialogProps) { + + const handleConfirm = async () => { + await onClose(true); + }; + + const handleCancel = async () => { + onClose(false); + }; + + const ActionButton = styled(LoadingButton) ` + width: auto; + border-radius: 5px; + margin: 0 5px 10px 5px; + ` + + return ( + + + {title} + + + {children} + + + + {btnCancelText} + + + {btnConfirmText} + + + + ); +}; diff --git a/admin-ui/src/component/ElectionForm.tsx b/admin-ui/src/component/ElectionForm.tsx index 9ecb2a5..264c539 100644 --- a/admin-ui/src/component/ElectionForm.tsx +++ b/admin-ui/src/component/ElectionForm.tsx @@ -10,6 +10,7 @@ import { Alert, Link, } from "@mui/material"; +import CircularProgress from '@mui/material/CircularProgress'; import { Election, ElectionConfiguration, @@ -60,6 +61,9 @@ import Loading from "./Loading"; import { dateToYMD, formatTimeStamp } from "dsl/date"; import InputEnumSelect from "./InputEnumSelect"; import { eachHourOfInterval } from "date-fns"; +import ConfirmationDialog from "./ConfirmationDialog"; +import LoadingButton from "./LoadingButton"; +import useCurrentElection from "hooks/useCurrentElection"; interface ElectionFormProps { election: Maybe; @@ -74,8 +78,9 @@ export default function ElectionForm({ }: ElectionFormProps) { const [step, setStepData] = useState(0); const [data, setData] = useState>(election); - const [currentElection, setReferenceCurrentElection] = - useState>(); + const [currentElection, reloadCurrentElection, loadingCurrentElection] = useCurrentElection(); + // const [currentElection, setReferenceCurrentElection] = + // useState>(); const [alertText, setAlertText] = useState(""); const setStep = (step: number): void => { @@ -120,6 +125,8 @@ export default function ElectionForm({ election?.testVotersFile ? { status: "started" } : {} ); + const [currentElectionDialogOpen, setCurrentElectionDialogOpen] = useState(false); + const router = useRouter(); const steps = [ @@ -199,13 +206,50 @@ export default function ElectionForm({ handleDataChange(name, formattedDate); }; + const handleSetCurrentElection = async () => { + if (currentElection) { + setCurrentElectionDialogOpen(true); + } else { + await saveAsCurrentElection(); + } + }; + + const handleCurrentElectionDialogClose = async (confirmed: boolean) => { + + if (confirmed) { + await saveAsCurrentElection(); + } else { + setCurrentElectionDialogOpen(false); + } + + }; + + const saveAsCurrentElection = async () => { + if (election) { + try { + await setCurrentElection(election.electionId); + } catch (e) { + console.log("setCurrentElection error"); + console.log(e); + messageError(e); + } finally { + await reloadElectionData(); + reloadCurrentElection(); + setCurrentElectionDialogOpen(false); + } + } + } + + + const reloadElectionData = async () => { if (election?.electionId) { const updatedElection = await getElection(election?.electionId); setData(updatedElection); + onUpdateElection(updatedElection); } - const currentElection = await adminGetCurrentElection(); - setReferenceCurrentElection(currentElection); + // const currentElection = await adminGetCurrentElection(); + // setReferenceCurrentElection(currentElection); }; const save = async () => { @@ -939,7 +983,7 @@ export default function ElectionForm({ ); - + let formContents: ReactNode = null; if (step === 0) { formContents = electionNameFields; @@ -956,21 +1000,22 @@ export default function ElectionForm({ } else if (step === 6) { formContents = reviewFields; } + const actions = ( - + {step > 0 && ( - + )} {step < steps.length - 1 && step !== 5 && ( - + )} {step === 4 && @@ -990,14 +1035,16 @@ export default function ElectionForm({ }} > {(election?.testCount ?? 0) >= 1 ? "Continue" : "Begin"} Testing + + ) : ( step === 4 && ( - + {step === 4 && election && - currentElection && + !loadingCurrentElection && currentElection && currentElection?.electionId !== election.electionId && (

This election is not the current election and so cannot be set @@ -1010,28 +1057,30 @@ export default function ElectionForm({ (!election.electionStatus || election.electionStatus === ElectionStatus.draft) && (!currentElection || - (currentElection && + (!loadingCurrentElection && currentElection && currentElection?.electionId !== election?.electionId && currentElection?.latMode !== 1 && currentElection?.electionStatus !== ElectionStatus.open)) && ( - + + + + It will archive the election for {currentElection?.electionJurisdictionName} {currentElection?.electionName}. + + + )} ) @@ -1043,15 +1092,15 @@ export default function ElectionForm({ currentElection?.electionId === election.electionId && (election?.testCount ?? 0) >= 1 && !election?.testComplete && ( - + )} {step === steps.length - 1 && ( + + + ); +}; + diff --git a/admin-ui/src/hooks/useCurrentElection.ts b/admin-ui/src/hooks/useCurrentElection.ts index 3caa068..bebac5b 100644 --- a/admin-ui/src/hooks/useCurrentElection.ts +++ b/admin-ui/src/hooks/useCurrentElection.ts @@ -16,6 +16,7 @@ export default function useCurrentElection(): [ const [election, setElection] = useState>(null); const loadElection = async () => { + setLoading(true); let e = await adminGetCurrentElection(); //CTW shouldn't be doing it this way now diff --git a/examples/edf-validation/.DS_Store b/examples/edf-validation/.DS_Store index e447cce5b73734f190284e91b3c594cad6415344..bed5ad0fe6350ead4b0bb4b3e8b1cba6eed6fd47 100644 GIT binary patch delta 288 zcmZoMXfc@JFUroqz`)4BAi&_6lb@WFlb;0S3v3o-Ue2fql44;Ee9B1 diff --git a/local-env-osx.json b/local-env-osx.json index 2b68c01..a798bb4 100644 --- a/local-env-osx.json +++ b/local-env-osx.json @@ -1,7 +1,7 @@ { "Parameters": { "DEV_ENVIRONMENT": "OSX", - "UPLOAD_BUCKET": "abc-uploads-development-vadminui", - "ELECTIONS_DOCUMENT_BUCKET": "abc-documents-development-vadminui" + "UPLOAD_BUCKET": "abc-uploads-development-radmin", + "ELECTIONS_DOCUMENT_BUCKET": "abc-documents-development-radmin" } } \ No newline at end of file diff --git a/template.yaml b/template.yaml index d7c9976..2a86e16 100644 --- a/template.yaml +++ b/template.yaml @@ -10,7 +10,7 @@ Parameters: # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: - Timeout: 15 + Timeout: 300 Handler: app.lambdaHandler Runtime: nodejs18.x Environment: @@ -941,6 +941,7 @@ Resources: CodeUri: endpoints/setElectionVoters/ Layers: - !Ref LibLayer + MemorySize: 512 Events: AbcBackendApiGateway: Type: Api From 5900a4d2d2c65d931e2cfc2d990c13ee403fac21 Mon Sep 17 00:00:00 2001 From: Renu Date: Fri, 24 Mar 2023 22:29:36 +0530 Subject: [PATCH 04/13] Fix issue 113 --- admin-ui/pages/elections/[id]/open-test.tsx | 231 +++++++++++++------- lib/Election.js | 10 + 2 files changed, 165 insertions(+), 76 deletions(-) diff --git a/admin-ui/pages/elections/[id]/open-test.tsx b/admin-ui/pages/elections/[id]/open-test.tsx index 0488e7e..2da12c1 100644 --- a/admin-ui/pages/elections/[id]/open-test.tsx +++ b/admin-ui/pages/elections/[id]/open-test.tsx @@ -1,4 +1,4 @@ -import { Button, Grid, Slider, SliderThumb, Typography } from "@mui/material"; +import { Alert, Button, Grid, Slider, SliderThumb, Typography } from "@mui/material"; import LoggedInLayout from "layout/LoggedInLayout"; import type { NextPage } from "next"; import { useRouter } from "next/router"; @@ -15,8 +15,9 @@ import { import GC from "component/GC"; import GI from "component/GI"; import Loading from "component/Loading"; -import ElectionCard from "component/ElectionCard"; import { useNavigate } from "react-router-dom"; +import useCurrentElection from "hooks/useCurrentElection"; +import LoadingButton from "component/LoadingButton"; interface ThumbProps { children: ReactNode; @@ -40,13 +41,18 @@ const TestElection: NextPage = () => { const { id } = query; const electionId = Array.isArray(id) ? id[0] : id; - + const [currentElection, reloadCurrentElection, loadingCurrentElection] = useCurrentElection(); const [election, setElection] = useState>(null); + + const shouldShowSetCurrentBtn = !loadingCurrentElection && currentElection && currentElection?.electionId != electionId; + const [alertText, setAlertText] = useState(""); + const [maliciousRequest, setMaliciousRequest] = useState(false); const loadElection = async () => { if (electionId) { const resp = await getElection(electionId); setElection(resp); + } }; @@ -56,13 +62,38 @@ const TestElection: NextPage = () => { } }, [electionId]); + useEffect(() => { + const modesNotAllowedForTest = [ElectionStatus.archived, ElectionStatus.closed, ElectionStatus.open]; + if (election && modesNotAllowedForTest.includes(election.electionStatus)) { + // setAlertText(`${resp?.electionName} is ${resp.electionStatus} and so cannot be set to test mode.`) + setMaliciousRequest(true); + } + }, [election]); + const testElection = async () => { if (electionId) { - await setCurrentElection(electionId); - await openElectionTest(electionId); - router.push("/dashboard"); + try { + await openElectionTest(electionId); + router.push("/dashboard"); + } catch (e: any) { + console.error(e); + setAlertText(e?.data?.error_description); + } + // await setCurrentElection(electionId); + } + // loadElection(); + }; + + const runSetCurrentElection = async () => { + if (electionId) { + try { + await setCurrentElection(electionId); + reloadCurrentElection(); + } catch (e: any) { + console.error(e); + setAlertText(`${currentElection?.electionJurisdictionName} ${currentElection?.electionName} ${e?.data?.error_description}`); + } } - loadElection(); }; const navigate = useNavigate(); @@ -71,83 +102,131 @@ const TestElection: NextPage = () => { navigate(-1); }; - return ( - - {!election && } - {election && - election.latMode == - 1 /*election?.electionStatus === ElectionStatus.test */ && ( - + const InTestMode = () => { + return ( + + + You are now in Testing Mode! + + + You are now in testing mode! Please test your election with your + team, then come back to finish editing and launch your election! + + + - You are now in Testing Mode! + - - You are now in testing mode! Please test your election with your - team, then come back to finish editing and launch your election! - - - - - - - - - + - )} - {election && !election?.latMode && ( - // This should be latMode 0; but some elections don't have that? - //== 0 /*election?.electionStatus !== ElectionStatus.test */ && ( - <> - Please confirm to continue. - - Please confirm that you would like to enter testing mode for{" "} - {election?.electionName}. - - - + + + ) + } + + const NeedCurrentMode = () => { + return ( + + + Prerequisites not met for test mode! + + + Election for {election?.electionJurisdictionName} {election?.electionName} is not the current election and so cannot be set to test mode. + + + + - - -   - - - { - if (newValue === 100) { - testElection(); - } - }} - components={{ - Thumb: ThumbComponent, - }} - step={null} - marks={[ - { - value: 0, - label: "", - }, - { - value: 100, - label: "", - }, - ]} - defaultValue={0} - /> - + + {!alertText ? + + + Set Current + + + : false + } + + + + ) + } + + const SetTestMode = () => { + return ( + <> + Please confirm to continue. + + Please confirm that you would like to enter testing mode for{" "} + {election?.electionName}. + + + + + + +   + + { + if (newValue === 100) { + testElection(); + } + }} + components={{ + Thumb: ThumbComponent, + }} + step={null} + marks={[ + { + value: 0, + label: "", + }, + { + value: 100, + label: "", + }, + ]} + defaultValue={0} + /> + + + + ) + } + + return ( + + {(!election || loadingCurrentElection) && } + {maliciousRequest ? + {election?.electionName} is {election?.electionStatus} and so cannot be set to test mode. + : + <> + {!loadingCurrentElection && election && + election.latMode == 1 /*election?.electionStatus === ElectionStatus.test */ && + + } + {!loadingCurrentElection && election && !election?.latMode && ( + // This should be latMode 0; but some elections don't have that? + //== 0 /*election?.electionStatus !== ElectionStatus.test */ && ( + shouldShowSetCurrentBtn ? : + )} + + {alertText && {alertText}} + - )} + } ); }; diff --git a/lib/Election.js b/lib/Election.js index 5edbb2c..42da46c 100644 --- a/lib/Election.js +++ b/lib/Election.js @@ -1433,6 +1433,16 @@ class Election { case apiEndpoint.setCurrentElection: return Election.testConditions(endpoint, election, [ [liveCurrentElection, "Current election is in a live state"], + [ + electionAttributes?.["electionStatus"] == + Election.electionStatus.archived, + "Election has been archived.", + ], + [ + electionAttributes?.["electionStatus"] == + Election.electionStatus.closed, + "Election has been closed", + ] ]); case apiEndpoint.openElectionTest: From 5584cf8f427aaee15cfff2f3d6f8ca62f0f5baee Mon Sep 17 00:00:00 2001 From: Renu Date: Tue, 4 Apr 2023 08:55:24 +0530 Subject: [PATCH 05/13] mid way commit --- admin-ui/pages/elections/[id]/edit.tsx | 43 +-- .../elections/[id]/steps/election-name.tsx | 64 +++++ .../[id]/steps/election-settings.tsx | 66 +++++ admin-ui/src/component/Input.tsx | 4 +- .../component/election-steps/ElectionName.tsx | 252 +++++++++++++++++ .../election-steps/ElectionSettings.tsx | 263 ++++++++++++++++++ admin-ui/src/context/ElectionContext.tsx | 55 ++++ admin-ui/src/hooks/useElection.ts | 54 ++++ admin-ui/src/layout/ElectionPageLayout.tsx | 60 ++++ 9 files changed, 840 insertions(+), 21 deletions(-) create mode 100644 admin-ui/pages/elections/[id]/steps/election-name.tsx create mode 100644 admin-ui/pages/elections/[id]/steps/election-settings.tsx create mode 100644 admin-ui/src/component/election-steps/ElectionName.tsx create mode 100644 admin-ui/src/component/election-steps/ElectionSettings.tsx create mode 100644 admin-ui/src/context/ElectionContext.tsx create mode 100644 admin-ui/src/hooks/useElection.ts create mode 100644 admin-ui/src/layout/ElectionPageLayout.tsx diff --git a/admin-ui/pages/elections/[id]/edit.tsx b/admin-ui/pages/elections/[id]/edit.tsx index 301d0fa..ebb5564 100644 --- a/admin-ui/pages/elections/[id]/edit.tsx +++ b/admin-ui/pages/elections/[id]/edit.tsx @@ -22,40 +22,43 @@ import Section from "component/Section"; import ElectionCard from "component/ElectionCard"; import { Box } from "@mui/system"; import ElectionForm from "component/ElectionForm"; +import ElectionName from "component/election-steps/ElectionName"; +import ElectionPageLayout from "layout/ElectionPageLayout"; +import useElection from "hooks/useElection"; const NewElection: NextPage = () => { - const [election, setElection] = useState>(null); - + // const [election, setElection] = useState>(null); const router = useRouter(); const { query } = router; const { id } = query; - + const electionId = Array.isArray(id) ? id[0] : id; + const [election, loading] = useElection(electionId); - useEffect(() => { - const loadElection = async () => { - if (electionId) { - const resp = await getElection(electionId); - // const configuration = await getConfigurations(electionId); - // resp.configuration = configuration; - setElection(resp); - } - }; - if (electionId) { - loadElection(); - } - }, [electionId]); + // useEffect(() => { + // const loadElection = async () => { + // if (electionId) { + // const resp = await getElection(electionId); + // // const configuration = await getConfigurations(electionId); + // // resp.configuration = configuration; + // setElection(resp); + // } + // }; + // if (electionId) { + // loadElection(); + // } + // }, [electionId]); return ( - + {election && ( - setElection(e)} + onUpdateElection={(e) => {}} /> )} - + ); }; diff --git a/admin-ui/pages/elections/[id]/steps/election-name.tsx b/admin-ui/pages/elections/[id]/steps/election-name.tsx new file mode 100644 index 0000000..92e4b7d --- /dev/null +++ b/admin-ui/pages/elections/[id]/steps/election-name.tsx @@ -0,0 +1,64 @@ +import type { NextPage } from "next"; +import LoggedInLayout from "layout/LoggedInLayout"; +import { + Button, + Card, + Grid, + Paper, + TextField, + Typography, +} from "@mui/material"; +import { useContext, useEffect, useState } from "react"; + +import Input from "component/Input"; + +import theme from "theme"; +import UserContext from "context/UserContext"; +import { requestLoginCode } from "requests/auth"; +import { useRouter } from "next/router"; +import { Election, ElectionCreate, Maybe } from "types"; +import { getAll as getAllElections, getElection } from "requests/election"; +import Section from "component/Section"; +import ElectionCard from "component/ElectionCard"; +import { Box } from "@mui/system"; +import ElectionForm from "component/ElectionForm"; +import ElectionName from "component/election-steps/ElectionName"; +import ElectionPageLayout from "layout/ElectionPageLayout"; +import useElection from "hooks/useElection"; + +const EditElectionName: NextPage = () => { + // const [election, setElection] = useState>(null); + const router = useRouter(); + const { query } = router; + const { id } = query; + + const electionId = Array.isArray(id) ? id[0] : id; + // const [election, loading] = useElection(electionId); + + // useEffect(() => { + // const loadElection = async () => { + // if (electionId) { + // const resp = await getElection(electionId); + // // const configuration = await getConfigurations(electionId); + // // resp.configuration = configuration; + // setElection(resp); + // } + // }; + // if (electionId) { + // loadElection(); + // } + // }, [electionId]); + + return ( + + {electionId && + + } + + ); +}; + +export default EditElectionName; diff --git a/admin-ui/pages/elections/[id]/steps/election-settings.tsx b/admin-ui/pages/elections/[id]/steps/election-settings.tsx new file mode 100644 index 0000000..025e842 --- /dev/null +++ b/admin-ui/pages/elections/[id]/steps/election-settings.tsx @@ -0,0 +1,66 @@ +import type { NextPage } from "next"; +import LoggedInLayout from "layout/LoggedInLayout"; +import { + Button, + Card, + Grid, + Paper, + TextField, + Typography, +} from "@mui/material"; +import { useContext, useEffect, useState } from "react"; + +import Input from "component/Input"; + +import theme from "theme"; +import UserContext from "context/UserContext"; +import { requestLoginCode } from "requests/auth"; +import { useRouter } from "next/router"; +import { Election, ElectionCreate, Maybe } from "types"; +import { getAll as getAllElections, getElection } from "requests/election"; +import Section from "component/Section"; +import ElectionCard from "component/ElectionCard"; +import { Box } from "@mui/system"; +import ElectionForm from "component/ElectionForm"; +import ElectionName from "component/election-steps/ElectionName"; +import ElectionPageLayout from "layout/ElectionPageLayout"; +import ElectionSettings from "component/election-steps/ElectionSettings"; +import useElection from "hooks/useElection"; + +const Settings: NextPage = () => { + // const [election, setElection] = useState>(null); + const router = useRouter(); + const { query } = router; + const { id } = query; + + const electionId = Array.isArray(id) ? id[0] : id; + const [election, loading] = useElection(electionId); + + // useEffect(() => { + // const loadElection = async () => { + // if (electionId) { + // const resp = await getElection(electionId); + // // const configuration = await getConfigurations(electionId); + // // resp.configuration = configuration; + // setElection(resp); + // } + // }; + // if (electionId) { + // loadElection(); + // } + // }, [electionId]); + + return ( + + {election && ( + {}} + /> + )} + + ); +}; + +export default Settings; diff --git a/admin-ui/src/component/Input.tsx b/admin-ui/src/component/Input.tsx index 149f711..52f3cf7 100644 --- a/admin-ui/src/component/Input.tsx +++ b/admin-ui/src/component/Input.tsx @@ -12,6 +12,7 @@ export interface InputProps { multiline?: boolean minRows?: number data?: Maybe<{[x: string]: any}> + readOnly?: boolean; onChange?: (name: string, value: string) => void } @@ -24,6 +25,7 @@ export default function Input({ multiline, minRows, data, + readOnly, ...props }: InputProps ) { const [visited, setVisited] = useState(false) @@ -40,7 +42,7 @@ export default function Input({ {label && {label}} { onChange && onChange(name, event.target.value); - }} onBlur={()=>{setVisited(true)}} id={name} placeholder={placeholder} minRows={minRows} {...props} /> + }} onBlur={()=>{setVisited(true)}} id={name} placeholder={placeholder} minRows={minRows} readOnly={readOnly} {...props} /> {helpText && {helpText}} } \ No newline at end of file diff --git a/admin-ui/src/component/election-steps/ElectionName.tsx b/admin-ui/src/component/election-steps/ElectionName.tsx new file mode 100644 index 0000000..a3730f3 --- /dev/null +++ b/admin-ui/src/component/election-steps/ElectionName.tsx @@ -0,0 +1,252 @@ +import { + Button, + Grid, + Typography, + Alert, +} from "@mui/material"; +import CircularProgress from '@mui/material/CircularProgress'; +import { + Election, + ElectionConfiguration, + ElectionCreate, + ElectionStatus, + Maybe, +} from "types"; + +import Input from "component/Input"; +import DatePicker from "component/DatePicker"; + +import NavigateNextIcon from "@mui/icons-material/NavigateNext"; +import NavigateBeforeIcon from "@mui/icons-material/NavigateBefore"; +import { + Dispatch, + ReactNode, + SetStateAction, + useEffect, + useState, +} from "react"; + +import { + setElectionAttributes, + createElection, + getElection, + setElectionConfigurations, +} from "requests/election"; +import { useRouter } from "next/router"; + +import { dateToYMD, formatTimeStamp } from "dsl/date"; +import { eachHourOfInterval } from "date-fns"; +import useCurrentElection from "hooks/useCurrentElection"; +import LoadingButton from "component/LoadingButton"; +import useElection from "hooks/useElection"; +import Loading from "component/Loading"; +import { Form } from "react-router-dom"; + +interface ElectionFormProps { + electionId: string; + title: string; + viewOnly?: boolean; +} + +export default function ElectionName({ + electionId, + title, + viewOnly = false +}: ElectionFormProps) { + + const {election, loading, saveElection} = useElection(electionId); + const [data, setData] = useState>(null); + + const [alertText, setAlertText] = useState(""); + const setAlert = (text: string) => { + setAlertText(text); + setTimeout(() => setAlertText(""), 4000); + }; + + const router = useRouter(); + + useEffect(() => { + setData(election); + }, [election]); + + const handleConfigurationChange = (name: string, value: any) => { + const newData = { ...(data || {}) } as { [x: string]: any }; + newData.configurations = {...(data?.configurations || {})} as ElectionConfiguration; + newData.configurations[name] = value; + console.log('Election', JSON.stringify(election) === JSON.stringify(newData)); + console.log('NewData', newData); + setData(newData as Election); + }; + + const handleDataChange = (name: string, value: any) => { + const newData = { ...data } as { [x: string]: any }; + newData[name] = value; + console.log('Election', election); + console.log('NewData', newData); + console.log(typeof setData); + setData(newData as Election); + }; + + const handleDateDataChange = (name: string, value: any) => { + const formattedDate = dateToYMD(value); + handleDataChange(name, formattedDate); + }; + + const save = async () => { + console.log('Election', JSON.stringify(election) === JSON.stringify(data)); + data && saveElection(data); + }; + + const messageError = (e: any) => { + setAlert( + e?.data?.error_description || + e?.data?.message || + e?.message || + JSON.stringify(e) + ); + }; + + const handleCancel = () => { + setData(election); + }; + + const saveNext = async () => { + try { + await save(); + router.push( + `/elections/${(data as Election)?.electionId}/steps/election-settings` + ); + } catch (e) { + messageError(e); + } + }; + + const startDateError = data?.electionVotingStartDate === undefined; + const startDateErrorProps = startDateError + ? { + helperText: "Input is required", + error: true, + } + : {}; + + const endDateError = data?.electionVotingEndDate === undefined; + const endDateErrorProps = endDateError + ? { + helperText: "Input is required", + error: true, + } + : {}; + + const electionNameFields = ( + + + + + + + + + + + + + + + + + + + + + + + + ); + + + const actions = ( + + + + + + } onClick={saveNext}> + Save + + + + ); + + + return ( + <> + {loading ? ( + + ) : ( + election && ( + + {electionNameFields} + + {alertText && {alertText}} + + {!viewOnly && {actions}} + + ) + )} + + ); +} diff --git a/admin-ui/src/component/election-steps/ElectionSettings.tsx b/admin-ui/src/component/election-steps/ElectionSettings.tsx new file mode 100644 index 0000000..b508a93 --- /dev/null +++ b/admin-ui/src/component/election-steps/ElectionSettings.tsx @@ -0,0 +1,263 @@ +import { + Grid, + Typography, + InputLabel, + Alert, + Button, +} from "@mui/material"; +import CircularProgress from '@mui/material/CircularProgress'; +import { + Election, + ElectionConfiguration, + ElectionCreate, + Maybe, +} from "types"; + + +import Input from "component/Input"; + +import NavigateNextIcon from "@mui/icons-material/NavigateNext"; +import NavigateBeforeIcon from "@mui/icons-material/NavigateBefore"; +import CheckIcon from "@mui/icons-material/Check"; +import { + useEffect, + useState, +} from "react"; + +import { + setElectionAttributes, + createElection, + getElection, + setElectionConfigurations, +} from "requests/election"; +import { useRouter } from "next/router"; + +import GC from "component/GC"; +import GI from "component/GI"; +import InputEnumSelect from "component/InputEnumSelect"; +import InputSwitch from "component/InputSwitch"; +import LoadingButton from "component/LoadingButton"; +import Loading from "component/Loading"; +import useElection from "hooks/useElection"; + +interface ElectionSettingsProps { + electionId: string; + title: string; + viewOnly?: boolean; +} + +export default function ElectionSettings({ + electionId, + title, + viewOnly = false +}: ElectionSettingsProps) { + const {election, loading, saveElection} = useElection(electionId); + const [data, setData] = useState>(null); + const [alertText, setAlertText] = useState(""); + + const setAlert = (text: string) => { + setAlertText(text); + setTimeout(() => setAlertText(""), 4000); + }; + + const router = useRouter(); + + useEffect(() => { + setData(election); + }, [election]); + + const handleConfigurationChange = (name: string, value: any) => { + const newData = { ...(data || {}) } as { [x: string]: any }; + newData.configurations = {...(data?.configurations || {})} as ElectionConfiguration; + newData.configurations[name] = value; + console.log('Election', JSON.stringify(election) === JSON.stringify(newData)); + console.log('NewData', newData); + setData(newData as Election); + }; + + + const save = async () => { + data && saveElection(data); + }; + + const messageError = (e: any) => { + setAlert( + e?.data?.error_description || + e?.data?.message || + e?.message || + JSON.stringify(e) + ); + }; + + const saveBack = async () => { + try { + // await save(); + router.push( + `/elections/${(data as Election)?.electionId}/edit` + ); + } catch (e) { + messageError(e); + } + }; + + const saveNext = async () => { + try { + await save(); + } catch (e) { + messageError(e); + } + }; + + const handleCancel = () => { + setData(election); + }; + + const electionSettingsFields = ( + + + Required Fields + + + + + { + handleConfigurationChange("absenteeStatusRequired", value); + }} + > + Require Absentee Status + + + + { + handleConfigurationChange( + "affidavitRequiresDLIDcardPhotos", + value + ); + }} + > + Affidavit requires ID Photo + + + + { + handleConfigurationChange("affidavitWitnessRequirement", value); + }} + options={[ + { value: "none", name: "None" }, + { value: "name", name: "Name" }, + { value: "nameSignature", name: "Name and Signature" }, + { value: "nameAddress", name: "Name and Address" }, + { + value: "nameSignatureAddress", + name: "Name Signature and Address", + }, + ]} + > + Affidavit witness requirement + + + + { + handleConfigurationChange("multipleUsePermitted", value); + }} + > + Multiple Use Permitted + + + + + { + handleConfigurationChange("DLNalpha", value); + }} + > + DLN contains alpha characters + + + + Drivers License Character Length + + + + + + + + + + + + + + + + ); + + const actions = ( + + + + + + } onClick={saveNext}> + Save + + + + ); + + return ( + <> + {loading ? ( + + ) : ( + election && ( + + {electionSettingsFields} + + {alertText && {alertText}} + + {!viewOnly && {actions}} + + ) + )} + + ); +} diff --git a/admin-ui/src/context/ElectionContext.tsx b/admin-ui/src/context/ElectionContext.tsx new file mode 100644 index 0000000..9939a8f --- /dev/null +++ b/admin-ui/src/context/ElectionContext.tsx @@ -0,0 +1,55 @@ +import React, { createContext, ReactNode, useState } from 'react'; +import { createElection, getElection, setElectionAttributes, setElectionConfigurations } from 'requests/election'; +import { Election, ElectionCreate, Maybe } from 'types'; + +interface ElectionContextType { + election: Maybe; + loadElection: (electionId: string) => void; + saveElection: (data: Election | ElectionCreate) => void; +} + +export const ElectionContext = createContext({ + election: null, + loadElection: (electionId: string):void => {}, + saveElection: (data: Election | ElectionCreate): void => {} +}); + +export const ElectionProvider: React.FC = ({ children }) => { + const [election, setElection] = useState>(null); + + const loadElection = async (electionId: string) => { + console.log('ELECTIONCONTEXT - ' + electionId) + if (electionId) { + const resp = await getElection(electionId); + setElection(resp); + } + }; + + const saveElection = async (data: Election | ElectionCreate) => { + try { + let updatedElection: Maybe = null; + if ((data as Election)?.electionId) { + updatedElection = await setElectionAttributes(data as Election); + } else { + updatedElection = await createElection(data as ElectionCreate); + } + if ((data as Election)?.configurations) { + updatedElection = await setElectionConfigurations( + updatedElection.electionId, + (data as Election)?.configurations + ); + } + setElection(updatedElection); + // onUpdateElection(updatedElection); TODO-R + } catch (e) { + console.log(e); + throw e; + } + } + + return ( + + {children} + + ); +}; diff --git a/admin-ui/src/hooks/useElection.ts b/admin-ui/src/hooks/useElection.ts new file mode 100644 index 0000000..e6e2900 --- /dev/null +++ b/admin-ui/src/hooks/useElection.ts @@ -0,0 +1,54 @@ +import { useCallback, useEffect, useState } from "react"; + +import { createElection, getAll as getAllElections, getElection, setElectionAttributes, setElectionConfigurations } from 'requests/election' +import { Election, ElectionCreate, Maybe } from "types"; + +type ElectionHookResult = { + election: Maybe; + loading: boolean; + loadElection: () => Promise; + saveElection: (data: Election | ElectionCreate) => Promise; +}; + +export default function useElection(electionId?: string): ElectionHookResult { + const [loading, setLoading] = useState(true); + const [election, setElection] = useState>(null); + + const loadElection = async () => { + if (electionId) { + setLoading(true); + const resp = await getElection(electionId); + setElection(resp); + setLoading(false); + } + }; + const fetchElection = useCallback(loadElection, [electionId]); + + const saveElection = async (data: Election | ElectionCreate) => { + try { + let updatedElection: Maybe = null; + if ((data as Election)?.electionId) { + updatedElection = await setElectionAttributes(data as Election); + } else { + updatedElection = await createElection(data as ElectionCreate); + } + if ((data as Election)?.configurations) { + updatedElection = await setElectionConfigurations( + updatedElection.electionId, + (data as Election)?.configurations + ); + } + setElection(updatedElection); + // onUpdateElection(updatedElection); TODO-R + } catch (e) { + console.log(e); + throw e; + } + } + + useEffect(() => { + fetchElection() + }, [fetchElection]); + + return {election, loading, loadElection , saveElection}; +} \ No newline at end of file diff --git a/admin-ui/src/layout/ElectionPageLayout.tsx b/admin-ui/src/layout/ElectionPageLayout.tsx new file mode 100644 index 0000000..b9c7ca3 --- /dev/null +++ b/admin-ui/src/layout/ElectionPageLayout.tsx @@ -0,0 +1,60 @@ +import Grid from "@mui/material/Grid"; +import Step from "@mui/material/Step"; +import StepButton from "@mui/material/StepButton"; +import Stepper from "@mui/material/Stepper"; +import Typography from "@mui/material/Typography"; +import { ElectionProvider } from "context/ElectionContext"; +import { useRouter } from "next/router"; +import { ReactNode, useState } from "react"; +import LoggedInLayout from "./LoggedInLayout"; + +interface ElectionPageLayoutProps { + showStepper?: boolean; + step: number; + title: string; + electionId?: string; + children: ReactNode; +} + +export default function ElectionPageLayout({ + showStepper = false, + step, + electionId = "", + title, + children +}: ElectionPageLayoutProps) { + const router = useRouter(); + const stepRouteBaseURL = `/elections/${electionId}/steps`; + + const steps = [ + {label: "Election Name", route: `election-name`}, + {label: "Election Settings", route: `election-settings`}, + {label: "Upload EDF", route: `edf`}, + {label: "Upload Ballots", route: `ballots`}, + {label: "Test Election", route: `test`}, + {label: "Production Voter Data", route: `production-voter`}, + {label: "Review", route: `review`} + ]; + + return ( + + + + {title} + + {children} + + + {steps.map((electionStep, index) => ( + index}> + router.push(`${stepRouteBaseURL}/${electionStep.route}`)}> + {electionStep.label} + + + ))} + + + + + ); +} From 77d720d2e6ed33c3ce307961905f4f9a34209fb1 Mon Sep 17 00:00:00 2001 From: Renu Date: Tue, 4 Apr 2023 18:24:20 +0530 Subject: [PATCH 06/13] add just for revew --- admin-ui/pages/elections/[id]/[step].tsx | 45 ++++++++++ admin-ui/pages/elections/[id]/edit.tsx | 45 +++++----- .../elections/[id]/steps/election-name.tsx | 64 -------------- .../[id]/steps/election-settings.tsx | 66 --------------- admin-ui/pages/elections/new.tsx | 7 +- admin-ui/src/component/ElectionCard.tsx | 4 +- .../component/election-steps/ElectionName.tsx | 84 ++++++++----------- .../election-steps/ElectionSettings.tsx | 57 +++++++------ admin-ui/src/context/ElectionContext.tsx | 41 ++++----- .../{useElection.ts => useSaveElection.ts} | 22 +---- admin-ui/src/layout/ElectionPageLayout.tsx | 2 +- admin-ui/src/types/election.ts | 20 +++++ 12 files changed, 176 insertions(+), 281 deletions(-) create mode 100644 admin-ui/pages/elections/[id]/[step].tsx delete mode 100644 admin-ui/pages/elections/[id]/steps/election-name.tsx delete mode 100644 admin-ui/pages/elections/[id]/steps/election-settings.tsx rename admin-ui/src/hooks/{useElection.ts => useSaveElection.ts} (66%) diff --git a/admin-ui/pages/elections/[id]/[step].tsx b/admin-ui/pages/elections/[id]/[step].tsx new file mode 100644 index 0000000..664107d --- /dev/null +++ b/admin-ui/pages/elections/[id]/[step].tsx @@ -0,0 +1,45 @@ +import type { NextPage } from "next"; + +import { useRouter } from "next/router"; +import { StepsRoutes } from "types"; +import ElectionName from "component/election-steps/ElectionName"; +import ElectionPageLayout from "layout/ElectionPageLayout"; +import ElectionSettings from "component/election-steps/ElectionSettings"; +import { ElectionProvider } from "context/ElectionContext"; + +const EditElectionName: NextPage = () => { + // const [election, setElection] = useState>(null); + const router = useRouter(); + const { query } = router; + const { id, step, view } = query; + + const electionId = Array.isArray(id) ? id[0] : id; + const stepName = Array.isArray(step) ? step[0] : step; + const viewOnly:boolean = !!view; + const activeStepIndex = stepName ? Object.values(StepsRoutes).indexOf(stepName as StepsRoutes) : 0; + + let component = null; + + switch (stepName) { + case StepsRoutes.ElectionName: + component = ; + break; + case StepsRoutes.ElectionSettings: + component = ; + break; + } + + if (!component) { + router.push("/dashboard"); + } + + return ( + + + {component} + + + ); +}; + +export default EditElectionName; diff --git a/admin-ui/pages/elections/[id]/edit.tsx b/admin-ui/pages/elections/[id]/edit.tsx index ebb5564..d6a9d24 100644 --- a/admin-ui/pages/elections/[id]/edit.tsx +++ b/admin-ui/pages/elections/[id]/edit.tsx @@ -22,44 +22,41 @@ import Section from "component/Section"; import ElectionCard from "component/ElectionCard"; import { Box } from "@mui/system"; import ElectionForm from "component/ElectionForm"; -import ElectionName from "component/election-steps/ElectionName"; -import ElectionPageLayout from "layout/ElectionPageLayout"; -import useElection from "hooks/useElection"; const NewElection: NextPage = () => { - // const [election, setElection] = useState>(null); + const [election, setElection] = useState>(null); + const router = useRouter(); const { query } = router; const { id } = query; - + const electionId = Array.isArray(id) ? id[0] : id; - const [election, loading] = useElection(electionId); - // useEffect(() => { - // const loadElection = async () => { - // if (electionId) { - // const resp = await getElection(electionId); - // // const configuration = await getConfigurations(electionId); - // // resp.configuration = configuration; - // setElection(resp); - // } - // }; - // if (electionId) { - // loadElection(); - // } - // }, [electionId]); + useEffect(() => { + const loadElection = async () => { + if (electionId) { + const resp = await getElection(electionId); + // const configuration = await getConfigurations(electionId); + // resp.configuration = configuration; + setElection(resp); + } + }; + if (electionId) { + loadElection(); + } + }, [electionId]); return ( - + {election && ( - {}} + onUpdateElection={(e) => setElection(e)} /> )} - + ); }; -export default NewElection; +export default NewElection; \ No newline at end of file diff --git a/admin-ui/pages/elections/[id]/steps/election-name.tsx b/admin-ui/pages/elections/[id]/steps/election-name.tsx deleted file mode 100644 index 92e4b7d..0000000 --- a/admin-ui/pages/elections/[id]/steps/election-name.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import type { NextPage } from "next"; -import LoggedInLayout from "layout/LoggedInLayout"; -import { - Button, - Card, - Grid, - Paper, - TextField, - Typography, -} from "@mui/material"; -import { useContext, useEffect, useState } from "react"; - -import Input from "component/Input"; - -import theme from "theme"; -import UserContext from "context/UserContext"; -import { requestLoginCode } from "requests/auth"; -import { useRouter } from "next/router"; -import { Election, ElectionCreate, Maybe } from "types"; -import { getAll as getAllElections, getElection } from "requests/election"; -import Section from "component/Section"; -import ElectionCard from "component/ElectionCard"; -import { Box } from "@mui/system"; -import ElectionForm from "component/ElectionForm"; -import ElectionName from "component/election-steps/ElectionName"; -import ElectionPageLayout from "layout/ElectionPageLayout"; -import useElection from "hooks/useElection"; - -const EditElectionName: NextPage = () => { - // const [election, setElection] = useState>(null); - const router = useRouter(); - const { query } = router; - const { id } = query; - - const electionId = Array.isArray(id) ? id[0] : id; - // const [election, loading] = useElection(electionId); - - // useEffect(() => { - // const loadElection = async () => { - // if (electionId) { - // const resp = await getElection(electionId); - // // const configuration = await getConfigurations(electionId); - // // resp.configuration = configuration; - // setElection(resp); - // } - // }; - // if (electionId) { - // loadElection(); - // } - // }, [electionId]); - - return ( - - {electionId && - - } - - ); -}; - -export default EditElectionName; diff --git a/admin-ui/pages/elections/[id]/steps/election-settings.tsx b/admin-ui/pages/elections/[id]/steps/election-settings.tsx deleted file mode 100644 index 025e842..0000000 --- a/admin-ui/pages/elections/[id]/steps/election-settings.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import type { NextPage } from "next"; -import LoggedInLayout from "layout/LoggedInLayout"; -import { - Button, - Card, - Grid, - Paper, - TextField, - Typography, -} from "@mui/material"; -import { useContext, useEffect, useState } from "react"; - -import Input from "component/Input"; - -import theme from "theme"; -import UserContext from "context/UserContext"; -import { requestLoginCode } from "requests/auth"; -import { useRouter } from "next/router"; -import { Election, ElectionCreate, Maybe } from "types"; -import { getAll as getAllElections, getElection } from "requests/election"; -import Section from "component/Section"; -import ElectionCard from "component/ElectionCard"; -import { Box } from "@mui/system"; -import ElectionForm from "component/ElectionForm"; -import ElectionName from "component/election-steps/ElectionName"; -import ElectionPageLayout from "layout/ElectionPageLayout"; -import ElectionSettings from "component/election-steps/ElectionSettings"; -import useElection from "hooks/useElection"; - -const Settings: NextPage = () => { - // const [election, setElection] = useState>(null); - const router = useRouter(); - const { query } = router; - const { id } = query; - - const electionId = Array.isArray(id) ? id[0] : id; - const [election, loading] = useElection(electionId); - - // useEffect(() => { - // const loadElection = async () => { - // if (electionId) { - // const resp = await getElection(electionId); - // // const configuration = await getConfigurations(electionId); - // // resp.configuration = configuration; - // setElection(resp); - // } - // }; - // if (electionId) { - // loadElection(); - // } - // }, [electionId]); - - return ( - - {election && ( - {}} - /> - )} - - ); -}; - -export default Settings; diff --git a/admin-ui/pages/elections/new.tsx b/admin-ui/pages/elections/new.tsx index ab4529d..4591d4e 100644 --- a/admin-ui/pages/elections/new.tsx +++ b/admin-ui/pages/elections/new.tsx @@ -22,6 +22,7 @@ import Section from "component/Section"; import ElectionCard from "component/ElectionCard"; import { Box } from "@mui/system"; import ElectionForm from "component/ElectionForm"; +import ElectionName from "component/election-steps/ElectionName"; const NewElection: NextPage = () => { const [election, setElection] = useState>(null); @@ -32,11 +33,7 @@ const NewElection: NextPage = () => { return ( - + ); }; diff --git a/admin-ui/src/component/ElectionCard.tsx b/admin-ui/src/component/ElectionCard.tsx index 7288d61..9a21ef1 100644 --- a/admin-ui/src/component/ElectionCard.tsx +++ b/admin-ui/src/component/ElectionCard.tsx @@ -9,7 +9,7 @@ import { openElectionLookup, setElectionTestComplete, } from "requests/election"; -import { Election, ElectionServingStatus, ElectionStatus, Maybe } from "types"; +import { Election, ElectionServingStatus, ElectionStatus, Maybe, StepsRoutes } from "types"; import CompletedCheckbox from "./CompletedCheckbox"; interface ElectionCardProps { @@ -164,7 +164,7 @@ export default function ElectionCard({ - } onClick={saveNext}> + } onClick={save}> Save @@ -234,18 +224,16 @@ export default function ElectionName({ return ( <> - {loading ? ( + {!!!election && mode != 'create' ? ( ) : ( - election && ( - - {electionNameFields} - - {alertText && {alertText}} - - {!viewOnly && {actions}} + + {electionNameFields} + + {alertText && {alertText}} - ) + {!viewOnly && {actions}} + )} ); diff --git a/admin-ui/src/component/election-steps/ElectionSettings.tsx b/admin-ui/src/component/election-steps/ElectionSettings.tsx index b508a93..37d6eb1 100644 --- a/admin-ui/src/component/election-steps/ElectionSettings.tsx +++ b/admin-ui/src/component/election-steps/ElectionSettings.tsx @@ -5,7 +5,6 @@ import { Alert, Button, } from "@mui/material"; -import CircularProgress from '@mui/material/CircularProgress'; import { Election, ElectionConfiguration, @@ -18,18 +17,12 @@ import Input from "component/Input"; import NavigateNextIcon from "@mui/icons-material/NavigateNext"; import NavigateBeforeIcon from "@mui/icons-material/NavigateBefore"; -import CheckIcon from "@mui/icons-material/Check"; import { + useContext, useEffect, useState, } from "react"; -import { - setElectionAttributes, - createElection, - getElection, - setElectionConfigurations, -} from "requests/election"; import { useRouter } from "next/router"; import GC from "component/GC"; @@ -38,20 +31,22 @@ import InputEnumSelect from "component/InputEnumSelect"; import InputSwitch from "component/InputSwitch"; import LoadingButton from "component/LoadingButton"; import Loading from "component/Loading"; -import useElection from "hooks/useElection"; +import { ElectionContext } from "context/ElectionContext"; +import useSaveElection from "hooks/useSaveElection"; interface ElectionSettingsProps { - electionId: string; - title: string; + // electionId: string; + // title: string; viewOnly?: boolean; } export default function ElectionSettings({ - electionId, - title, + // electionId, + // title, viewOnly = false }: ElectionSettingsProps) { - const {election, loading, saveElection} = useElection(electionId); + const { election, updateElection} = useContext(ElectionContext); + const {election: updatedElection, saveElection} = useSaveElection(); const [data, setData] = useState>(null); const [alertText, setAlertText] = useState(""); @@ -66,18 +61,30 @@ export default function ElectionSettings({ setData(election); }, [election]); + useEffect(() => { + updatedElection && updateElection(updatedElection); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [updatedElection]); + const handleConfigurationChange = (name: string, value: any) => { const newData = { ...(data || {}) } as { [x: string]: any }; newData.configurations = {...(data?.configurations || {})} as ElectionConfiguration; newData.configurations[name] = value; - console.log('Election', JSON.stringify(election) === JSON.stringify(newData)); - console.log('NewData', newData); setData(newData as Election); }; const save = async () => { - data && saveElection(data); + try { + if (data) { + await saveElection(data); + router.push( + `/elections/${(data as Election)?.electionId}/edf` + ); + } + } catch (e) { + messageError(e); + } }; const messageError = (e: any) => { @@ -245,18 +252,16 @@ export default function ElectionSettings({ return ( <> - {loading ? ( + {!!!election ? ( ) : ( - election && ( - - {electionSettingsFields} - - {alertText && {alertText}} - - {!viewOnly && {actions}} + + {electionSettingsFields} + + {alertText && {alertText}} - ) + {!viewOnly && {actions}} + )} ); diff --git a/admin-ui/src/context/ElectionContext.tsx b/admin-ui/src/context/ElectionContext.tsx index 9939a8f..84d9f44 100644 --- a/admin-ui/src/context/ElectionContext.tsx +++ b/admin-ui/src/context/ElectionContext.tsx @@ -1,23 +1,30 @@ -import React, { createContext, ReactNode, useState } from 'react'; +import React, { createContext, ReactNode, useEffect, useState } from 'react'; import { createElection, getElection, setElectionAttributes, setElectionConfigurations } from 'requests/election'; import { Election, ElectionCreate, Maybe } from 'types'; interface ElectionContextType { election: Maybe; loadElection: (electionId: string) => void; - saveElection: (data: Election | ElectionCreate) => void; + updateElection: (data: Election) => void; } export const ElectionContext = createContext({ election: null, loadElection: (electionId: string):void => {}, - saveElection: (data: Election | ElectionCreate): void => {} + updateElection: (data: Election): void => {} }); -export const ElectionProvider: React.FC = ({ children }) => { +export const ElectionProvider = ({electionId, children }) => { const [election, setElection] = useState>(null); - const loadElection = async (electionId: string) => { + useEffect(() => { + console.log('In Context', electionId); + if (electionId) { + loadElection(); + } + }, [electionId]); + + const loadElection = async () => { console.log('ELECTIONCONTEXT - ' + electionId) if (electionId) { const resp = await getElection(electionId); @@ -25,30 +32,12 @@ export const ElectionProvider: React.FC = ({ children }) => { } }; - const saveElection = async (data: Election | ElectionCreate) => { - try { - let updatedElection: Maybe = null; - if ((data as Election)?.electionId) { - updatedElection = await setElectionAttributes(data as Election); - } else { - updatedElection = await createElection(data as ElectionCreate); - } - if ((data as Election)?.configurations) { - updatedElection = await setElectionConfigurations( - updatedElection.electionId, - (data as Election)?.configurations - ); - } - setElection(updatedElection); - // onUpdateElection(updatedElection); TODO-R - } catch (e) { - console.log(e); - throw e; - } + const updateElection = async (data: Election ) => { + data && setElection(data); } return ( - + {children} ); diff --git a/admin-ui/src/hooks/useElection.ts b/admin-ui/src/hooks/useSaveElection.ts similarity index 66% rename from admin-ui/src/hooks/useElection.ts rename to admin-ui/src/hooks/useSaveElection.ts index e6e2900..92c934d 100644 --- a/admin-ui/src/hooks/useElection.ts +++ b/admin-ui/src/hooks/useSaveElection.ts @@ -5,27 +5,15 @@ import { Election, ElectionCreate, Maybe } from "types"; type ElectionHookResult = { election: Maybe; - loading: boolean; - loadElection: () => Promise; saveElection: (data: Election | ElectionCreate) => Promise; }; -export default function useElection(electionId?: string): ElectionHookResult { - const [loading, setLoading] = useState(true); +export default function useSaveElection(): ElectionHookResult { const [election, setElection] = useState>(null); - - const loadElection = async () => { - if (electionId) { - setLoading(true); - const resp = await getElection(electionId); - setElection(resp); - setLoading(false); - } - }; - const fetchElection = useCallback(loadElection, [electionId]); const saveElection = async (data: Election | ElectionCreate) => { try { + setElection(null); let updatedElection: Maybe = null; if ((data as Election)?.electionId) { updatedElection = await setElectionAttributes(data as Election); @@ -46,9 +34,5 @@ export default function useElection(electionId?: string): ElectionHookResult { } } - useEffect(() => { - fetchElection() - }, [fetchElection]); - - return {election, loading, loadElection , saveElection}; + return {election, saveElection}; } \ No newline at end of file diff --git a/admin-ui/src/layout/ElectionPageLayout.tsx b/admin-ui/src/layout/ElectionPageLayout.tsx index b9c7ca3..db9e2c9 100644 --- a/admin-ui/src/layout/ElectionPageLayout.tsx +++ b/admin-ui/src/layout/ElectionPageLayout.tsx @@ -24,7 +24,7 @@ export default function ElectionPageLayout({ children }: ElectionPageLayoutProps) { const router = useRouter(); - const stepRouteBaseURL = `/elections/${electionId}/steps`; + const stepRouteBaseURL = `/elections/${electionId}/`; const steps = [ {label: "Election Name", route: `election-name`}, diff --git a/admin-ui/src/types/election.ts b/admin-ui/src/types/election.ts index f4afc87..a09ea6f 100644 --- a/admin-ui/src/types/election.ts +++ b/admin-ui/src/types/election.ts @@ -89,3 +89,23 @@ export type BallotFile = { ballotID: string; file: File; }; + +export enum StepsRoutes { + ElectionName = "election-name", + ElectionSettings = "election-settings", + UploadEDF = "edf", + UploadBallots = "ballots", + TestElection = "test", + ProductionVoterData = "production-voter", + Review = "review" +} + +export const ElectionSteps: { label: string; route: StepsRoutes }[] = [ + { label: "Election Name", route: StepsRoutes.ElectionName }, + { label: "Election Settings", route: StepsRoutes.ElectionSettings }, + { label: "Upload EDF", route: StepsRoutes.UploadEDF }, + { label: "Upload Ballots", route: StepsRoutes.UploadBallots }, + { label: "Test Election", route: StepsRoutes.TestElection }, + { label: "Production Voter Data", route: StepsRoutes.ProductionVoterData }, + { label: "Review", route: StepsRoutes.Review } +]; \ No newline at end of file From 6e667af8b20bce44b8eafd17a0e1a4e57ce6aba7 Mon Sep 17 00:00:00 2001 From: Renu Date: Tue, 4 Apr 2023 23:17:36 +0530 Subject: [PATCH 07/13] midway commit --- admin-ui/pages/elections/[id]/[step].tsx | 31 ++++++++------- admin-ui/pages/elections/new.tsx | 2 +- .../src/component/ElectionFormContainer.tsx | 38 +++++++++++++++++++ ...ionName.tsx => ElectionAttributesForm.tsx} | 20 ++++++---- ...ngs.tsx => ElectionConfigurationsForm.tsx} | 15 +++++--- 5 files changed, 75 insertions(+), 31 deletions(-) create mode 100644 admin-ui/src/component/ElectionFormContainer.tsx rename admin-ui/src/component/election-steps/{ElectionName.tsx => ElectionAttributesForm.tsx} (93%) rename admin-ui/src/component/election-steps/{ElectionSettings.tsx => ElectionConfigurationsForm.tsx} (95%) diff --git a/admin-ui/pages/elections/[id]/[step].tsx b/admin-ui/pages/elections/[id]/[step].tsx index 664107d..c2271cc 100644 --- a/admin-ui/pages/elections/[id]/[step].tsx +++ b/admin-ui/pages/elections/[id]/[step].tsx @@ -2,10 +2,9 @@ import type { NextPage } from "next"; import { useRouter } from "next/router"; import { StepsRoutes } from "types"; -import ElectionName from "component/election-steps/ElectionName"; import ElectionPageLayout from "layout/ElectionPageLayout"; -import ElectionSettings from "component/election-steps/ElectionSettings"; import { ElectionProvider } from "context/ElectionContext"; +import ElectionFormContainer from "component/ElectionFormContainer"; const EditElectionName: NextPage = () => { // const [election, setElection] = useState>(null); @@ -18,25 +17,25 @@ const EditElectionName: NextPage = () => { const viewOnly:boolean = !!view; const activeStepIndex = stepName ? Object.values(StepsRoutes).indexOf(stepName as StepsRoutes) : 0; - let component = null; + // let component = null; - switch (stepName) { - case StepsRoutes.ElectionName: - component = ; - break; - case StepsRoutes.ElectionSettings: - component = ; - break; - } - - if (!component) { - router.push("/dashboard"); - } + // switch (stepName) { + // case StepsRoutes.ElectionName: + // component = ; + // break; + // case StepsRoutes.ElectionSettings: + // component = ; + // break; + // } + // if (!component) { + // router.push("/dashboard"); + // } + return ( - {component} + ); diff --git a/admin-ui/pages/elections/new.tsx b/admin-ui/pages/elections/new.tsx index 4591d4e..5f969ae 100644 --- a/admin-ui/pages/elections/new.tsx +++ b/admin-ui/pages/elections/new.tsx @@ -22,7 +22,7 @@ import Section from "component/Section"; import ElectionCard from "component/ElectionCard"; import { Box } from "@mui/system"; import ElectionForm from "component/ElectionForm"; -import ElectionName from "component/election-steps/ElectionName"; +import ElectionName from "component/election-steps/ElectionAttributesForm"; const NewElection: NextPage = () => { const [election, setElection] = useState>(null); diff --git a/admin-ui/src/component/ElectionFormContainer.tsx b/admin-ui/src/component/ElectionFormContainer.tsx new file mode 100644 index 0000000..37c725a --- /dev/null +++ b/admin-ui/src/component/ElectionFormContainer.tsx @@ -0,0 +1,38 @@ +import { ElectionContext } from "context/ElectionContext"; +import router from "next/router"; +import { useContext, useState } from "react"; +import { Election, Maybe, StepsRoutes } from "types"; +import ElectionAttributesForm from "./election-steps/ElectionAttributesForm"; +import ElectionConfigurationsForm from "./election-steps/ElectionConfigurationsForm"; +import Loading from "./Loading"; + +interface ElectionFormContainerProps { + stepName: string | undefined; + viewOnly?: boolean; +} + +export default function ElectionFormContainer({ + stepName, + viewOnly = false +}: ElectionFormContainerProps) { + + const {election, updateElection } = useContext(ElectionContext); + + const StepComponent = () => { + switch (stepName) { + case StepsRoutes.ElectionName: + return ; + case StepsRoutes.ElectionSettings: + return ; + default: + return null; + } + }; + + return ( + <> + {!!!election && } + {election && } + + ); +} \ No newline at end of file diff --git a/admin-ui/src/component/election-steps/ElectionName.tsx b/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx similarity index 93% rename from admin-ui/src/component/election-steps/ElectionName.tsx rename to admin-ui/src/component/election-steps/ElectionAttributesForm.tsx index 328498b..cb3cd64 100644 --- a/admin-ui/src/component/election-steps/ElectionName.tsx +++ b/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx @@ -29,21 +29,25 @@ import Loading from "component/Loading"; import { ElectionContext } from "context/ElectionContext"; import useSaveElection from "hooks/useSaveElection"; -interface ElectionFormProps { +interface ElectionAttributesFormProps { // electionId: string; // title: string; + election: Maybe; + onUpdateElection(election: Election): void; viewOnly?: boolean; mode?: string; } -export default function ElectionName({ +export default function ElectionAttributesForm({ // electionId, // title, + election, + onUpdateElection, viewOnly = false, mode = '' -}: ElectionFormProps) { +}: ElectionAttributesFormProps) { - const { election, updateElection: updateElectionInCtx} = useContext(ElectionContext); + // const { election, updateElection: updateElectionInCtx} = useContext(ElectionContext); const {election: updatedElection, saveElection} = useSaveElection(); const [data, setData] = useState>(election); @@ -56,13 +60,13 @@ export default function ElectionName({ const router = useRouter(); - useEffect(() => { - setData(election); - }, [election]); + // useEffect(() => { + // setData(election); + // }, [election]); useEffect(() => { if (updatedElection) { - updateElectionInCtx(updatedElection); + onUpdateElection(updatedElection); router.push( `/elections/${(updatedElection as Election)?.electionId}/election-settings` ); diff --git a/admin-ui/src/component/election-steps/ElectionSettings.tsx b/admin-ui/src/component/election-steps/ElectionConfigurationsForm.tsx similarity index 95% rename from admin-ui/src/component/election-steps/ElectionSettings.tsx rename to admin-ui/src/component/election-steps/ElectionConfigurationsForm.tsx index 37d6eb1..30b0bec 100644 --- a/admin-ui/src/component/election-steps/ElectionSettings.tsx +++ b/admin-ui/src/component/election-steps/ElectionConfigurationsForm.tsx @@ -34,20 +34,23 @@ import Loading from "component/Loading"; import { ElectionContext } from "context/ElectionContext"; import useSaveElection from "hooks/useSaveElection"; -interface ElectionSettingsProps { +interface ElectionConfigurationsFormProps { // electionId: string; // title: string; + election: Maybe; + onUpdateElection(election: Election): void; viewOnly?: boolean; } -export default function ElectionSettings({ +export default function ElectionConfigurationsForm({ // electionId, // title, + election, + onUpdateElection, viewOnly = false -}: ElectionSettingsProps) { - const { election, updateElection} = useContext(ElectionContext); +}: ElectionConfigurationsFormProps) { const {election: updatedElection, saveElection} = useSaveElection(); - const [data, setData] = useState>(null); + const [data, setData] = useState>(election); const [alertText, setAlertText] = useState(""); const setAlert = (text: string) => { @@ -62,7 +65,7 @@ export default function ElectionSettings({ }, [election]); useEffect(() => { - updatedElection && updateElection(updatedElection); + updatedElection && onUpdateElection(updatedElection); // eslint-disable-next-line react-hooks/exhaustive-deps }, [updatedElection]); From 2b2daf0fbc8728ed9d32070810291d4fc4f16500 Mon Sep 17 00:00:00 2001 From: rgunpal Date: Thu, 6 Apr 2023 16:32:24 +0530 Subject: [PATCH 08/13] Delete .DS_Store --- examples/edf-validation/.DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/edf-validation/.DS_Store diff --git a/examples/edf-validation/.DS_Store b/examples/edf-validation/.DS_Store deleted file mode 100644 index bed5ad0fe6350ead4b0bb4b3e8b1cba6eed6fd47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKL2KJE6n;vxIVKcx*r1nzq1TY6*(j7<+%zrpT95V6mdvib&6{V1*lD04;Pd*2 z`b+j-cG|utsi8?*=xK~i51u~h={?E%WF$Qz68%ZMP1GWy29&YpqIf{KpY@*9jKu*8 z9^)vJ7f~i7w0S@)e?J4fcXf&>q>{pw{o6xd=a&+-kcD3~EkolG7;Io_tWmry%emFkm);lS)=(Zk4@6%*DY`xf*c^Rg8GFAa;oWSMmB+X+v z?aN6X7b-T;9j@DOhpmm-thc?>_If+7=WTDc+v~QyH@m(0+-#>-%wXEOf;8xPW?fD#&krUM$#A$91AQW`B&CSJkw zE@ck<6Dm-4=nEAq-uw}Fz1Qgy8Ek2w{9K7d(~4!lGGG~aIs@ixY&4%9 zVm&PbmVy6-0X`o*D5Gz%(x{FO6e Date: Thu, 6 Apr 2023 16:37:15 +0530 Subject: [PATCH 09/13] add warning for unsaved data --- admin-ui/pages/elections/[id]/[step].tsx | 3 + .../src/component/ElectionFormContainer.tsx | 8 +- .../election-steps/ElectionAttributesForm.tsx | 29 +- admin-ui/src/context/ElectionContext.tsx | 9 +- admin-ui/src/dsl/date.ts | 2 +- admin-ui/src/hooks/useWarningIfUnsavedData.ts | 39 ++ admin-ui/src/layout/ElectionPageLayout.tsx | 2 +- package-lock.json | 523 ++++++++++++++++++ yarn.lock | 108 ++-- 9 files changed, 647 insertions(+), 76 deletions(-) create mode 100644 admin-ui/src/hooks/useWarningIfUnsavedData.ts create mode 100644 package-lock.json diff --git a/admin-ui/pages/elections/[id]/[step].tsx b/admin-ui/pages/elections/[id]/[step].tsx index c2271cc..7e22b1d 100644 --- a/admin-ui/pages/elections/[id]/[step].tsx +++ b/admin-ui/pages/elections/[id]/[step].tsx @@ -5,6 +5,7 @@ import { StepsRoutes } from "types"; import ElectionPageLayout from "layout/ElectionPageLayout"; import { ElectionProvider } from "context/ElectionContext"; import ElectionFormContainer from "component/ElectionFormContainer"; +import { useEffect } from "react"; const EditElectionName: NextPage = () => { // const [election, setElection] = useState>(null); @@ -17,6 +18,8 @@ const EditElectionName: NextPage = () => { const viewOnly:boolean = !!view; const activeStepIndex = stepName ? Object.values(StepsRoutes).indexOf(stepName as StepsRoutes) : 0; + + // let component = null; // switch (stepName) { diff --git a/admin-ui/src/component/ElectionFormContainer.tsx b/admin-ui/src/component/ElectionFormContainer.tsx index 37c725a..55ece62 100644 --- a/admin-ui/src/component/ElectionFormContainer.tsx +++ b/admin-ui/src/component/ElectionFormContainer.tsx @@ -1,6 +1,5 @@ import { ElectionContext } from "context/ElectionContext"; -import router from "next/router"; -import { useContext, useState } from "react"; +import { useContext, useEffect, useLayoutEffect, useState } from "react"; import { Election, Maybe, StepsRoutes } from "types"; import ElectionAttributesForm from "./election-steps/ElectionAttributesForm"; import ElectionConfigurationsForm from "./election-steps/ElectionConfigurationsForm"; @@ -17,7 +16,7 @@ export default function ElectionFormContainer({ }: ElectionFormContainerProps) { const {election, updateElection } = useContext(ElectionContext); - + const StepComponent = () => { switch (stepName) { case StepsRoutes.ElectionName: @@ -31,8 +30,7 @@ export default function ElectionFormContainer({ return ( <> - {!!!election && } - {election && } + {election && } ); } \ No newline at end of file diff --git a/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx b/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx index cb3cd64..26250eb 100644 --- a/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx +++ b/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx @@ -28,6 +28,7 @@ import LoadingButton from "component/LoadingButton"; import Loading from "component/Loading"; import { ElectionContext } from "context/ElectionContext"; import useSaveElection from "hooks/useSaveElection"; +import { useWarningIfUnsavedData } from "hooks/useWarningIfUnsavedData"; interface ElectionAttributesFormProps { // electionId: string; @@ -49,10 +50,10 @@ export default function ElectionAttributesForm({ // const { election, updateElection: updateElectionInCtx} = useContext(ElectionContext); const {election: updatedElection, saveElection} = useSaveElection(); - const [data, setData] = useState>(election); - + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [alertText, setAlertText] = useState(""); + const setAlert = (text: string) => { setAlertText(text); setTimeout(() => setAlertText(""), 4000); @@ -60,10 +61,6 @@ export default function ElectionAttributesForm({ const router = useRouter(); - // useEffect(() => { - // setData(election); - // }, [election]); - useEffect(() => { if (updatedElection) { onUpdateElection(updatedElection); @@ -74,21 +71,39 @@ export default function ElectionAttributesForm({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [updatedElection]); + useWarningIfUnsavedData(hasUnsavedChanges); + + const checkForChanges = (newData: Election) => { + const initialFormData = JSON.stringify(election); + const initialFormConfigurations = JSON.stringify(election?.configurations || {}); + + const currentFormData = JSON.stringify(newData); + const currentFormConfigurations = JSON.stringify(newData?.configurations || {}); + + const hasFormChanges = initialFormData != currentFormData || initialFormConfigurations != currentFormConfigurations; + console.log ('hasFormChanges',newData, election, hasFormChanges); + setHasUnsavedChanges(hasFormChanges); + } + const handleConfigurationChange = (name: string, value: any) => { const newData = { ...(data || {}) } as { [x: string]: any }; newData.configurations = {...(data?.configurations || {})} as ElectionConfiguration; newData.configurations[name] = value; setData(newData as Election); + checkForChanges(newData as Election); }; const handleDataChange = (name: string, value: any) => { const newData = { ...data } as { [x: string]: any }; newData[name] = value; + setData(newData as Election); + checkForChanges(newData as Election); }; const handleDateDataChange = (name: string, value: any) => { const formattedDate = dateToYMD(value); + console.log('formattedDate ', formattedDate) handleDataChange(name, formattedDate); }; @@ -178,7 +193,7 @@ export default function ElectionAttributesForm({ ({ updateElection: (data: Election): void => {} }); -export const ElectionProvider = ({electionId, children }) => { +type ElectionProviderProps = { + electionId: string; + children: ReactNode; +}; + +export const ElectionProvider = ({electionId, children }: ElectionProviderProps) => { const [election, setElection] = useState>(null); useEffect(() => { - console.log('In Context', electionId); if (electionId) { loadElection(); } }, [electionId]); const loadElection = async () => { - console.log('ELECTIONCONTEXT - ' + electionId) if (electionId) { const resp = await getElection(electionId); setElection(resp); diff --git a/admin-ui/src/dsl/date.ts b/admin-ui/src/dsl/date.ts index ae72a75..b8eceb6 100644 --- a/admin-ui/src/dsl/date.ts +++ b/admin-ui/src/dsl/date.ts @@ -32,7 +32,7 @@ export const dateToYMD = (date: Date | null | undefined): string => { } try { - return date.toLocaleDateString(); + return date.toLocaleDateString("en-US"); } catch (e) { console.log("Error formatting date", date); return ""; diff --git a/admin-ui/src/hooks/useWarningIfUnsavedData.ts b/admin-ui/src/hooks/useWarningIfUnsavedData.ts new file mode 100644 index 0000000..8e5e01f --- /dev/null +++ b/admin-ui/src/hooks/useWarningIfUnsavedData.ts @@ -0,0 +1,39 @@ +import { Router } from "next/router" +import { useEffect } from "react" + +export const useWarningIfUnsavedData = (unsavedChanges: boolean) => { + + const confirmMessage = "Your changes have not been saved. Do you want to discard them and leave this page?"; + + const handleRouteChangeStart = () => { + if (unsavedChanges) { + if (!window.confirm(confirmMessage)) { + Router.events.emit('routeChangeError'); + throw 'Route change cancelled. Ignore this error.'; + } + } + }; + + const handleBeforeUnload = (event: BeforeUnloadEvent) => { + event.preventDefault(); + (event || window.event).returnValue = confirmMessage; + return confirmMessage; // Gecko + Webkit, Safari, Chrome etc. + }; + + useEffect(() => { + if (unsavedChanges) { + window.addEventListener('beforeunload', handleBeforeUnload); + Router.events.on('routeChangeStart', handleRouteChangeStart); + } else { + window.removeEventListener('beforeunload', handleBeforeUnload); + Router.events.off('routeChangeStart', handleRouteChangeStart); + } + return () => { + window.removeEventListener('beforeunload', handleBeforeUnload); + Router.events.off('routeChangeStart', handleRouteChangeStart); + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [unsavedChanges]); + +} \ No newline at end of file diff --git a/admin-ui/src/layout/ElectionPageLayout.tsx b/admin-ui/src/layout/ElectionPageLayout.tsx index db9e2c9..389c244 100644 --- a/admin-ui/src/layout/ElectionPageLayout.tsx +++ b/admin-ui/src/layout/ElectionPageLayout.tsx @@ -24,7 +24,7 @@ export default function ElectionPageLayout({ children }: ElectionPageLayoutProps) { const router = useRouter(); - const stepRouteBaseURL = `/elections/${electionId}/`; + const stepRouteBaseURL = `/elections/${electionId}`; const steps = [ {label: "Election Name", route: `election-name`}, diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..dc9ded1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,523 @@ +{ + "name": "abc-backend", + "version": "3.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "abc-backend", + "version": "3.2.0", + "license": "MIT", + "dependencies": { + "ajv": "4", + "s3-csv-to-json": "^0.0.8", + "yauzl": "^2.10.0" + } + }, + "node_modules/ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha512-I/bSHSNEcFFqXLf91nchoNB9D1Kie3QKcWdchYUaoIg1+1bdWDkdfdlvdIOJbi9U8xR0y+MWc5D+won9v95WlQ==", + "dependencies": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sdk": { + "version": "2.1267.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1267.0.tgz", + "integrity": "sha512-ANTtRay26WwNRbYs6eZYN71b3DURNfWaq3AD6BtVNa8fVvnSLn+NNINw2+vLRjDLPZXMAQVHm0qH/TmyBvtjRA==", + "optional": true, + "dependencies": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.4.19" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "optional": true, + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "optional": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/csv-parser": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-2.3.5.tgz", + "integrity": "sha512-LCHolC4AlNwL+5EuD5LH2VVNKpD8QixZW2zzK1XmrVYUaslFY4c5BooERHOCIubG9iv/DAyFjs4x0HvWNZuyWg==", + "dependencies": { + "minimist": "^1.2.0", + "through2": "^3.0.1" + }, + "bin": { + "csv-parser": "bin/csv-parser" + }, + "engines": { + "node": ">= 8.16.0" + } + }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "optional": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "optional": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "optional": true + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "optional": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "optional": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "optional": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "optional": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "optional": true + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "optional": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "optional": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "optional": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "optional": true + }, + "node_modules/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha512-i/J297TW6xyj7sDFa7AmBPkQvLIxWr2kKPWI26tXydnZrzVAocNqn5DMNT1Mzk0vit1V5UkRM7C1KdVNp7Lmcg==", + "dependencies": { + "jsonify": "~0.0.0" + } + }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/parse-s3-bucket-key": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/parse-s3-bucket-key/-/parse-s3-bucket-key-0.0.1.tgz", + "integrity": "sha512-LqYkqubLAFzzx4VHnc6HhYwqlp24udXC3r0xBWgp963pbEKaNQNTEngygfTujxBglxMNZ8pf0BDprr/ooxEcTQ==" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "optional": true + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "optional": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/s3-csv-to-json": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/s3-csv-to-json/-/s3-csv-to-json-0.0.8.tgz", + "integrity": "sha512-6IMHz+U6B35/GRTMW/G7SymnW71M3A1DXgS3P3yvRoVhq+S5QuJ4OvxUGMH18H56VahfLNY32Mh/Ve/QJNmvlg==", + "dependencies": { + "csv-parser": "^2.0.0", + "parse-s3-bucket-key": "0.0.1" + }, + "optionalDependencies": { + "aws-sdk": "^2.329.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", + "optional": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "optional": true, + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "optional": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "optional": true, + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "node_modules/xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/yarn.lock b/yarn.lock index ba3046a..9ba57c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4,7 +4,7 @@ ajv@4: version "4.11.8" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + resolved "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz" integrity sha512-I/bSHSNEcFFqXLf91nchoNB9D1Kie3QKcWdchYUaoIg1+1bdWDkdfdlvdIOJbi9U8xR0y+MWc5D+won9v95WlQ== dependencies: co "^4.6.0" @@ -12,12 +12,12 @@ ajv@4: available-typed-arrays@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== aws-sdk@^2.329.0: version "2.1267.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1267.0.tgz#8f45c7bc7efb89a757526d993c5f77a2e7208676" + resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1267.0.tgz" integrity sha512-ANTtRay26WwNRbYs6eZYN71b3DURNfWaq3AD6BtVNa8fVvnSLn+NNINw2+vLRjDLPZXMAQVHm0qH/TmyBvtjRA== dependencies: buffer "4.9.2" @@ -33,17 +33,17 @@ aws-sdk@^2.329.0: base64-js@^1.0.2: version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== buffer-crc32@~0.2.3: version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== buffer@4.9.2: version "4.9.2" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + resolved "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz" integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== dependencies: base64-js "^1.0.2" @@ -52,7 +52,7 @@ buffer@4.9.2: call-bind@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== dependencies: function-bind "^1.1.1" @@ -60,12 +60,12 @@ call-bind@^1.0.2: co@^4.6.0: version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== csv-parser@^2.0.0: version "2.3.5" - resolved "https://registry.yarnpkg.com/csv-parser/-/csv-parser-2.3.5.tgz#6b3bf0907684914ff2c5abfbadab111a69eae5db" + resolved "https://registry.npmjs.org/csv-parser/-/csv-parser-2.3.5.tgz" integrity sha512-LCHolC4AlNwL+5EuD5LH2VVNKpD8QixZW2zzK1XmrVYUaslFY4c5BooERHOCIubG9iv/DAyFjs4x0HvWNZuyWg== dependencies: minimist "^1.2.0" @@ -73,31 +73,31 @@ csv-parser@^2.0.0: events@1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + resolved "https://registry.npmjs.org/events/-/events-1.1.1.tgz" integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw== fd-slicer@~1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz" integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== dependencies: pend "~1.2.0" for-each@^0.3.3: version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz" integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== dependencies: is-callable "^1.1.3" function-bind@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== get-intrinsic@^1.0.2, get-intrinsic@^1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz" integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== dependencies: function-bind "^1.1.1" @@ -106,48 +106,43 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.3: gopd@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz" integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== dependencies: get-intrinsic "^1.1.3" has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== has-tostringtag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== dependencies: has-symbols "^1.0.2" has@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: function-bind "^1.1.1" -ieee754@1.1.13: +ieee754@^1.1.4, ieee754@1.1.13: version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== -ieee754@^1.1.4: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== is-arguments@^1.0.4: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz" integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== dependencies: call-bind "^1.0.2" @@ -155,19 +150,19 @@ is-arguments@^1.0.4: is-callable@^1.1.3: version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== is-generator-function@^1.0.7: version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz" integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== dependencies: has-tostringtag "^1.0.0" is-typed-array@^1.1.10, is-typed-array@^1.1.3: version "1.1.10" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" + resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz" integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== dependencies: available-typed-arrays "^1.0.5" @@ -178,54 +173,54 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.3: isarray@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== jmespath@0.16.0: version "0.16.0" - resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + resolved "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz" integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== json-stable-stringify@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + resolved "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz" integrity sha512-i/J297TW6xyj7sDFa7AmBPkQvLIxWr2kKPWI26tXydnZrzVAocNqn5DMNT1Mzk0vit1V5UkRM7C1KdVNp7Lmcg== dependencies: jsonify "~0.0.0" jsonify@~0.0.0: version "0.0.1" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" + resolved "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz" integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== minimist@^1.2.0: version "1.2.7" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz" integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== parse-s3-bucket-key@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/parse-s3-bucket-key/-/parse-s3-bucket-key-0.0.1.tgz#64842d742697f938e647ccfb96ae35510727d603" + resolved "https://registry.npmjs.org/parse-s3-bucket-key/-/parse-s3-bucket-key-0.0.1.tgz" integrity sha512-LqYkqubLAFzzx4VHnc6HhYwqlp24udXC3r0xBWgp963pbEKaNQNTEngygfTujxBglxMNZ8pf0BDprr/ooxEcTQ== pend@~1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== punycode@1.3.2: version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== querystring@0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== "readable-stream@2 || 3": version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== dependencies: inherits "^2.0.3" @@ -234,7 +229,7 @@ querystring@0.2.0: s3-csv-to-json@^0.0.8: version "0.0.8" - resolved "https://registry.yarnpkg.com/s3-csv-to-json/-/s3-csv-to-json-0.0.8.tgz#5a130344a97fcab822448eef55631e051132c1f7" + resolved "https://registry.npmjs.org/s3-csv-to-json/-/s3-csv-to-json-0.0.8.tgz" integrity sha512-6IMHz+U6B35/GRTMW/G7SymnW71M3A1DXgS3P3yvRoVhq+S5QuJ4OvxUGMH18H56VahfLNY32Mh/Ve/QJNmvlg== dependencies: csv-parser "^2.0.0" @@ -244,29 +239,24 @@ s3-csv-to-json@^0.0.8: safe-buffer@~5.2.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -sax@1.2.1: +sax@>=0.6.0, sax@1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz" integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== -sax@>=0.6.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - string_decoder@^1.1.1: version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" through2@^3.0.1: version "3.0.2" - resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4" + resolved "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz" integrity sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ== dependencies: inherits "^2.0.4" @@ -274,7 +264,7 @@ through2@^3.0.1: url@0.10.3: version "0.10.3" - resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + resolved "https://registry.npmjs.org/url/-/url-0.10.3.tgz" integrity sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ== dependencies: punycode "1.3.2" @@ -282,12 +272,12 @@ url@0.10.3: util-deprecate@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== util@^0.12.4: version "0.12.5" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + resolved "https://registry.npmjs.org/util/-/util-0.12.5.tgz" integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== dependencies: inherits "^2.0.3" @@ -298,12 +288,12 @@ util@^0.12.4: uuid@8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz" integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== which-typed-array@^1.1.2: version "1.1.9" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" + resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz" integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== dependencies: available-typed-arrays "^1.0.5" @@ -315,7 +305,7 @@ which-typed-array@^1.1.2: xml2js@0.4.19: version "0.4.19" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz" integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== dependencies: sax ">=0.6.0" @@ -323,12 +313,12 @@ xml2js@0.4.19: xmlbuilder@~9.0.1: version "9.0.7" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz" integrity sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ== yauzl@^2.10.0: version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz" integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== dependencies: buffer-crc32 "~0.2.3" From 263e3b7b9349136e1aea49e2c5dc671cf8b1e101 Mon Sep 17 00:00:00 2001 From: Renu Date: Sat, 8 Apr 2023 23:19:25 +0530 Subject: [PATCH 10/13] add all steps page --- admin-ui/pages/elections/[id]/[step].tsx | 32 +- admin-ui/pages/elections/new.tsx | 20 +- admin-ui/src/component/ElectionCard.tsx | 19 +- .../src/component/ElectionFormContainer.tsx | 44 ++- .../election-steps/ElectionAttributesForm.tsx | 59 ++-- .../ElectionBallotsFilesForm.tsx | 184 +++++++++++ .../ElectionConfigurationsForm.tsx | 73 ++--- .../ElectionDefinitionFileForm.tsx | 173 ++++++++++ .../ElectionProductionVotersForm.tsx | 176 ++++++++++ .../election-steps/ElectionReview.tsx | 74 +++++ .../election-steps/ElectionTestForm.tsx | 308 ++++++++++++++++++ admin-ui/src/context/ElectionContext.tsx | 112 ++++++- admin-ui/src/hooks/useWarningIfUnsavedData.ts | 13 +- admin-ui/src/layout/ElectionPageLayout.tsx | 57 ++-- admin-ui/src/types/election.ts | 12 +- 15 files changed, 1193 insertions(+), 163 deletions(-) create mode 100644 admin-ui/src/component/election-steps/ElectionBallotsFilesForm.tsx create mode 100644 admin-ui/src/component/election-steps/ElectionDefinitionFileForm.tsx create mode 100644 admin-ui/src/component/election-steps/ElectionProductionVotersForm.tsx create mode 100644 admin-ui/src/component/election-steps/ElectionReview.tsx create mode 100644 admin-ui/src/component/election-steps/ElectionTestForm.tsx diff --git a/admin-ui/pages/elections/[id]/[step].tsx b/admin-ui/pages/elections/[id]/[step].tsx index 7e22b1d..ed4eebc 100644 --- a/admin-ui/pages/elections/[id]/[step].tsx +++ b/admin-ui/pages/elections/[id]/[step].tsx @@ -1,7 +1,7 @@ import type { NextPage } from "next"; import { useRouter } from "next/router"; -import { StepsRoutes } from "types"; +import { ElectionViewQueryParam, StepsRoutes } from "types"; import ElectionPageLayout from "layout/ElectionPageLayout"; import { ElectionProvider } from "context/ElectionContext"; import ElectionFormContainer from "component/ElectionFormContainer"; @@ -11,32 +11,16 @@ const EditElectionName: NextPage = () => { // const [election, setElection] = useState>(null); const router = useRouter(); const { query } = router; - const { id, step, view } = query; - + const { id, step } = query; + const electionId = Array.isArray(id) ? id[0] : id; - const stepName = Array.isArray(step) ? step[0] : step; - const viewOnly:boolean = !!view; + const stepName = Array.isArray(step) ? step[0] : step || ''; + const viewOnly:boolean = query.hasOwnProperty(ElectionViewQueryParam); const activeStepIndex = stepName ? Object.values(StepsRoutes).indexOf(stepName as StepsRoutes) : 0; - - - - // let component = null; - - // switch (stepName) { - // case StepsRoutes.ElectionName: - // component = ; - // break; - // case StepsRoutes.ElectionSettings: - // component = ; - // break; - // } - - // if (!component) { - // router.push("/dashboard"); - // } - + const pageTitle = viewOnly ? 'View Election' : 'Update ELection'; + console.log('pageTitle', pageTitle); return ( - + diff --git a/admin-ui/pages/elections/new.tsx b/admin-ui/pages/elections/new.tsx index 5f969ae..04c91b0 100644 --- a/admin-ui/pages/elections/new.tsx +++ b/admin-ui/pages/elections/new.tsx @@ -15,26 +15,26 @@ import Input from "component/Input"; import theme from "theme"; import UserContext from "context/UserContext"; import { requestLoginCode } from "requests/auth"; -import { useRouter } from "next/router"; +import router, { useRouter } from "next/router"; import { Election, ElectionCreate, Maybe } from "types"; import { getAll as getAllElections } from "requests/election"; import Section from "component/Section"; import ElectionCard from "component/ElectionCard"; import { Box } from "@mui/system"; import ElectionForm from "component/ElectionForm"; -import ElectionName from "component/election-steps/ElectionAttributesForm"; +import ElectionAttributesForm from "component/election-steps/ElectionAttributesForm"; +import ElectionPageLayout from "layout/ElectionPageLayout"; const NewElection: NextPage = () => { - const [election, setElection] = useState>(null); - - const onUpdateElection = async (election: Election) => { - setElection(election); - }; + const handleCancel = () => { + router.push("/dashboard"); + } + return ( - - - + + + ); }; diff --git a/admin-ui/src/component/ElectionCard.tsx b/admin-ui/src/component/ElectionCard.tsx index f1cacff..3a4e80f 100644 --- a/admin-ui/src/component/ElectionCard.tsx +++ b/admin-ui/src/component/ElectionCard.tsx @@ -1,6 +1,7 @@ import CheckIcon from "@mui/icons-material/Check"; -import { Button, Card, Grid, Typography, Link } from "@mui/material"; +import { Button, Card, Grid, Typography } from "@mui/material"; import { formatLongDate } from "dsl/date"; +import Link from "next/link"; import router from "next/router"; import { useEffect, useState } from "react"; import { @@ -10,7 +11,7 @@ import { setElectionTestComplete, archiveElection } from "requests/election"; -import { Election, ElectionServingStatus, ElectionStatus, Maybe, StepsRoutes } from "types"; +import { Election, ElectionServingStatus, ElectionStatus, ElectionViewQueryParam, Maybe, StepsRoutes } from "types"; import CompletedCheckbox from "./CompletedCheckbox"; interface ElectionCardProps { @@ -43,12 +44,16 @@ export default function ElectionCard({ const { configurations } = election; return ( - + - - {election.electionJurisdictionName} - {election.electionName},{" "} - {formatLongDate(election.electionDate)} - + + + + {election.electionJurisdictionName} - {election.electionName},{" "} + {formatLongDate(election.electionDate)} + + + diff --git a/admin-ui/src/component/ElectionFormContainer.tsx b/admin-ui/src/component/ElectionFormContainer.tsx index 55ece62..f7adbdc 100644 --- a/admin-ui/src/component/ElectionFormContainer.tsx +++ b/admin-ui/src/component/ElectionFormContainer.tsx @@ -1,8 +1,15 @@ import { ElectionContext } from "context/ElectionContext"; -import { useContext, useEffect, useLayoutEffect, useState } from "react"; -import { Election, Maybe, StepsRoutes } from "types"; +import router from "next/router"; +import { useContext } from "react"; +import { StepsRoutes } from "types"; import ElectionAttributesForm from "./election-steps/ElectionAttributesForm"; +import ElectionBallotsFilesForm from "./election-steps/ElectionBallotsFilesForm"; import ElectionConfigurationsForm from "./election-steps/ElectionConfigurationsForm"; +import ElectionDefinitionFileForm from "./election-steps/ElectionDefinitionFileForm"; +import ElectionProductionVotersForm from "./election-steps/ElectionProductionVotersForm"; +import ElectionReview from "./election-steps/ElectionReview"; +import ElectionTestForm from "./election-steps/ElectionTestForm"; + import Loading from "./Loading"; interface ElectionFormContainerProps { @@ -16,21 +23,34 @@ export default function ElectionFormContainer({ }: ElectionFormContainerProps) { const {election, updateElection } = useContext(ElectionContext); + + const handleCancel = () => { + router.push("/dashboard"); + } - const StepComponent = () => { - switch (stepName) { - case StepsRoutes.ElectionName: - return ; - case StepsRoutes.ElectionSettings: - return ; - default: - return null; - } + const stepForms = { + [StepsRoutes.ElectionName]: ElectionAttributesForm, + [StepsRoutes.ElectionSettings]: ElectionConfigurationsForm, + [StepsRoutes.UploadEDF]: ElectionDefinitionFileForm, + [StepsRoutes.UploadBallots]: ElectionBallotsFilesForm, + [StepsRoutes.ProductionVoterData]: ElectionProductionVotersForm, + [StepsRoutes.TestElection]: ElectionTestForm, + [StepsRoutes.Review]: ElectionReview }; + + const CurrentForm = stepName ? stepForms[stepName as StepsRoutes] : null; return ( <> - {election && } + {!election && } + {election && CurrentForm && ( + + )} ); } \ No newline at end of file diff --git a/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx b/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx index 26250eb..2f74d9e 100644 --- a/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx +++ b/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx @@ -8,6 +8,7 @@ import { ElectionConfiguration, ElectionCreate, Maybe, + StepsRoutes, } from "types"; import Input from "component/Input"; @@ -28,13 +29,14 @@ import LoadingButton from "component/LoadingButton"; import Loading from "component/Loading"; import { ElectionContext } from "context/ElectionContext"; import useSaveElection from "hooks/useSaveElection"; -import { useWarningIfUnsavedData } from "hooks/useWarningIfUnsavedData"; +import useWarningIfUnsavedData from "hooks/useWarningIfUnsavedData"; interface ElectionAttributesFormProps { // electionId: string; // title: string; - election: Maybe; - onUpdateElection(election: Election): void; + election?: Maybe; + onUpdateElection?: (election: Election) => void; + onCancel(): void; viewOnly?: boolean; mode?: string; } @@ -44,6 +46,7 @@ export default function ElectionAttributesForm({ // title, election, onUpdateElection, + onCancel, viewOnly = false, mode = '' }: ElectionAttributesFormProps) { @@ -63,9 +66,11 @@ export default function ElectionAttributesForm({ useEffect(() => { if (updatedElection) { - onUpdateElection(updatedElection); + + onUpdateElection && onUpdateElection(updatedElection); + router.push( - `/elections/${(updatedElection as Election)?.electionId}/election-settings` + `/elections/${(updatedElection as Election)?.electionId}/${StepsRoutes.ElectionSettings}` ); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -81,7 +86,9 @@ export default function ElectionAttributesForm({ const currentFormConfigurations = JSON.stringify(newData?.configurations || {}); const hasFormChanges = initialFormData != currentFormData || initialFormConfigurations != currentFormConfigurations; - console.log ('hasFormChanges',newData, election, hasFormChanges); + console.log ('hasFormChanges ', hasFormChanges); + console.log ('Old data ', election); + console.log ('new data ',newData); setHasUnsavedChanges(hasFormChanges); } @@ -96,7 +103,7 @@ export default function ElectionAttributesForm({ const handleDataChange = (name: string, value: any) => { const newData = { ...data } as { [x: string]: any }; newData[name] = value; - + console.log('Value ', value) setData(newData as Election); checkForChanges(newData as Election); }; @@ -116,13 +123,14 @@ export default function ElectionAttributesForm({ ); }; - const handleCancel = () => { - setData(election); - }; + // const handleCancel = () => { + // router.push("/dashboard"); + // }; - const save = async () => { + const saveNext = async () => { try { if (data) { + setHasUnsavedChanges(false); await saveElection(data); } } catch (e) { @@ -146,7 +154,7 @@ export default function ElectionAttributesForm({ } : {}; - const electionNameFields = ( + const formFields = ( - - } onClick={save}> - Save + } onClick={saveNext}> + {mode == 'create' ? 'Create Election' : 'Save & Continue'} @@ -243,17 +251,16 @@ export default function ElectionAttributesForm({ return ( <> - {!!!election && mode != 'create' ? ( - - ) : ( - - {electionNameFields} - - {alertText && {alertText}} + {election && ( + + {formFields} + + {alertText && {alertText}} + + {!viewOnly && {actions}} - {!viewOnly && {actions}} - - )} + ) + } ); } diff --git a/admin-ui/src/component/election-steps/ElectionBallotsFilesForm.tsx b/admin-ui/src/component/election-steps/ElectionBallotsFilesForm.tsx new file mode 100644 index 0000000..39b9b77 --- /dev/null +++ b/admin-ui/src/component/election-steps/ElectionBallotsFilesForm.tsx @@ -0,0 +1,184 @@ +import { + Button, + Grid, + Alert, + Typography, + Box, + Link, +} from "@mui/material"; +import { + Election, + ElectionCreate, + Maybe, + StepsRoutes, +} from "types"; + + +import NavigateNextIcon from "@mui/icons-material/NavigateNext"; +import CheckIcon from "@mui/icons-material/Check"; +import { + useContext, + useEffect, + useState, +} from "react"; + +import { useRouter } from "next/router"; + +import LoadingButton from "component/LoadingButton"; +import Loading from "component/Loading"; +import CompletedCheckbox from "component/CompletedCheckbox"; +import FileUpload from "component/FileUpload"; +import { ElectionContext } from "context/ElectionContext"; +import { setElectionBallots, setElectionDefinition } from "requests/election"; + +interface ElectionBallotsFilesFormProps { + election: Maybe; + onUpdateElection(election: Election): void; + onCancel(): void; + viewOnly?: boolean; +} + +export default function ElectionBallotsFilesForm({ + election, + onUpdateElection, + onCancel, + viewOnly = false +}: ElectionBallotsFilesFormProps) { + + const { loadElection, ballotsStatus: ballotStatusInDB, updateBallotsStatus } = useContext(ElectionContext); + + const [ballotsStatus, setBallotsStatus] = useState<{ [x: string]: any }>(ballotStatusInDB); + + const router = useRouter(); + + useEffect(() => { + setBallotsStatus(ballotStatusInDB) + }, [ballotStatusInDB]); + + const saveNext = async () => { + router.push( + `/elections/${(election as Election)?.electionId}/${StepsRoutes.TestElection}` + ); + }; + + const formFields = ( + + + Upload Ballot Files + {ballotsStatus.status === "complete" ? ( +

Success! Ballots uploaded

+ ) : ( + <> + { + try { + if ((election as Election)?.electionId) { + setBallotsStatus({ status: "uploading" }); + const resp = await setElectionBallots( + (election as Election)?.electionId, + file + ); + updateBallotsStatus(resp.objectKey); + loadElection(); + } + return; + } catch (e: any) { + setBallotsStatus({ + status: "error", + message: e?.data?.error_description, + }); + // onUpdateElection(data as Election); + console.log(e); + } + }} + /> + + + {ballotsStatus.status === "error" && ( + + Error Processing File: {ballotsStatus.message} +

+ Please see: help information +

+
+ )} + {ballotsStatus.status === "uploading" && ( + + + + )} + {ballotsStatus.status === "started" && ( + + Ballot File Processing + + )} +
+ + )} +
+ + + Ballot checklist + + {ballotsStatus.status === "complete" && ( + + Ballots Uploaded + + )} + + + 0} + > + {(election as Election)?.ballotDefinitionCount || 0} ballot definitions + uploaded + + 0}> + {(election as Election)?.ballotCount || 0} ballot files uploaded + + +
+ ); + + + const actions = ( + + + + + + } onClick={saveNext}> + Continue + + + + ); + + + return ( + <> + {election && ( + + {formFields} + {!viewOnly && {actions}} + + )} + + ); +} diff --git a/admin-ui/src/component/election-steps/ElectionConfigurationsForm.tsx b/admin-ui/src/component/election-steps/ElectionConfigurationsForm.tsx index 30b0bec..7f8cffb 100644 --- a/admin-ui/src/component/election-steps/ElectionConfigurationsForm.tsx +++ b/admin-ui/src/component/election-steps/ElectionConfigurationsForm.tsx @@ -10,15 +10,14 @@ import { ElectionConfiguration, ElectionCreate, Maybe, + StepsRoutes, } from "types"; import Input from "component/Input"; import NavigateNextIcon from "@mui/icons-material/NavigateNext"; -import NavigateBeforeIcon from "@mui/icons-material/NavigateBefore"; import { - useContext, useEffect, useState, } from "react"; @@ -31,14 +30,15 @@ import InputEnumSelect from "component/InputEnumSelect"; import InputSwitch from "component/InputSwitch"; import LoadingButton from "component/LoadingButton"; import Loading from "component/Loading"; -import { ElectionContext } from "context/ElectionContext"; import useSaveElection from "hooks/useSaveElection"; +import useWarningIfUnsavedData from "hooks/useWarningIfUnsavedData"; interface ElectionConfigurationsFormProps { // electionId: string; // title: string; election: Maybe; onUpdateElection(election: Election): void; + onCancel(): void; viewOnly?: boolean; } @@ -47,11 +47,13 @@ export default function ElectionConfigurationsForm({ // title, election, onUpdateElection, + onCancel, viewOnly = false }: ElectionConfigurationsFormProps) { const {election: updatedElection, saveElection} = useSaveElection(); const [data, setData] = useState>(election); const [alertText, setAlertText] = useState(""); + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const setAlert = (text: string) => { setAlertText(text); @@ -60,34 +62,37 @@ export default function ElectionConfigurationsForm({ const router = useRouter(); + useWarningIfUnsavedData(hasUnsavedChanges); + useEffect(() => { setData(election); }, [election]); useEffect(() => { - updatedElection && onUpdateElection(updatedElection); + if (updatedElection) { + onUpdateElection(updatedElection); + router.push( + `/elections/${(updatedElection as Election)?.electionId}/${StepsRoutes.UploadEDF}` + ); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [updatedElection]); + const checkForChanges = (newData: Election) => { + const initialFormConfigurations = JSON.stringify(election?.configurations || {}); + const currentFormConfigurations = JSON.stringify(newData?.configurations || {}); + + const hasFormChanges = initialFormConfigurations != currentFormConfigurations; + console.log ('hasFormChanges',newData, election, hasFormChanges); + setHasUnsavedChanges(hasFormChanges); + } + const handleConfigurationChange = (name: string, value: any) => { const newData = { ...(data || {}) } as { [x: string]: any }; newData.configurations = {...(data?.configurations || {})} as ElectionConfiguration; newData.configurations[name] = value; setData(newData as Election); - }; - - - const save = async () => { - try { - if (data) { - await saveElection(data); - router.push( - `/elections/${(data as Election)?.electionId}/edf` - ); - } - } catch (e) { - messageError(e); - } + checkForChanges(newData as Election); }; const messageError = (e: any) => { @@ -99,30 +104,18 @@ export default function ElectionConfigurationsForm({ ); }; - const saveBack = async () => { - try { - // await save(); - router.push( - `/elections/${(data as Election)?.electionId}/edit` - ); - } catch (e) { - messageError(e); - } - }; - const saveNext = async () => { try { - await save(); + if (data) { + setHasUnsavedChanges(false); + await saveElection(data); + } } catch (e) { messageError(e); } }; - const handleCancel = () => { - setData(election); - }; - - const electionSettingsFields = ( + const formFields = ( Required Fields @@ -241,13 +234,13 @@ export default function ElectionConfigurationsForm({ const actions = ( - } onClick={saveNext}> - Save + Save & Continue @@ -255,11 +248,9 @@ export default function ElectionConfigurationsForm({ return ( <> - {!!!election ? ( - - ) : ( + {election && ( - {electionSettingsFields} + {formFields} {alertText && {alertText}} diff --git a/admin-ui/src/component/election-steps/ElectionDefinitionFileForm.tsx b/admin-ui/src/component/election-steps/ElectionDefinitionFileForm.tsx new file mode 100644 index 0000000..414353e --- /dev/null +++ b/admin-ui/src/component/election-steps/ElectionDefinitionFileForm.tsx @@ -0,0 +1,173 @@ +import { + Button, + Grid, + Alert, + Typography, + Box, + Link, +} from "@mui/material"; +import { + Election, + ElectionCreate, + Maybe, + StepsRoutes, +} from "types"; + + +import NavigateNextIcon from "@mui/icons-material/NavigateNext"; +import CheckIcon from "@mui/icons-material/Check"; +import { + useContext, + useEffect, + useState, +} from "react"; + +import { useRouter } from "next/router"; + +import LoadingButton from "component/LoadingButton"; +import Loading from "component/Loading"; +import CompletedCheckbox from "component/CompletedCheckbox"; +import FileUpload from "component/FileUpload"; +import { ElectionContext } from "context/ElectionContext"; +import { setElectionDefinition } from "requests/election"; + +interface ElectionDefinitionFileFormProps { + election: Maybe; + onUpdateElection(election: Election): void; + onCancel(): void; + viewOnly?: boolean; +} + +export default function ElectionDefinitionFileForm({ + election, + onUpdateElection, + onCancel, + viewOnly = false +}: ElectionDefinitionFileFormProps) { + + const { loadElection, edfStatus: edfStatusInDB, updateEDFStatus } = useContext(ElectionContext); + + const [edfStatus, setEDFStatus] = useState<{ [x: string]: any }>(edfStatusInDB); + + const router = useRouter(); + + useEffect(() => { + setEDFStatus(edfStatusInDB) + }, [edfStatusInDB]); + + const saveNext = async () => { + router.push( + `/elections/${(election as Election)?.electionId}/${StepsRoutes.UploadBallots}` + ); + }; + console.log('edfStatusInDB', edfStatusInDB.status, edfStatus); + const formFields = ( + + + Upload Election Definition File + {edfStatus.status === "complete" ? ( + <> +

Success! EDF uploaded

+ + ) : ( + <> + { + try { + if ((election as Election)?.electionId) { + setEDFStatus({ status: "uploading" }); + console.log('ELection ID', (election as Election)?.electionId); + const resp = await setElectionDefinition( + (election as Election)?.electionId, + file + ); + updateEDFStatus(resp.objectKey); + loadElection(); + return; + } + } catch (e: any) { + setEDFStatus({ + status: "error", + message: e?.data?.error_description, + }); + console.log(e); + } + }} + /> + + {edfStatus.status === "error" && ( + + Error Processing File: {edfStatus.message} +

+ Please see: help information +

+
+ )} + {edfStatus.status === "uploading" && ( + + + + )} + {edfStatus.status === "started" && ( + + EDF File Processing + + )} +
+ + )} +
+ + + + Ballot checklist + + {edfStatus.status === "complete" && ( + + EDF File Uploaded + + )} + + + 0} + > + {(election as Election)?.ballotDefinitionCount || 0} ballot definitions + uploaded + + +
+ ); + + + const actions = ( + + + + + + } onClick={saveNext}> + Continue + + + + ); + + + return ( + <> + {election && ( + + {formFields} + {!viewOnly && {actions}} + + )} + + ); +} diff --git a/admin-ui/src/component/election-steps/ElectionProductionVotersForm.tsx b/admin-ui/src/component/election-steps/ElectionProductionVotersForm.tsx new file mode 100644 index 0000000..368a832 --- /dev/null +++ b/admin-ui/src/component/election-steps/ElectionProductionVotersForm.tsx @@ -0,0 +1,176 @@ +import { + Button, + Grid, + Alert, + Typography, + Box, + Link, +} from "@mui/material"; +import { + Election, + ElectionCreate, + Maybe, + StepsRoutes, +} from "types"; + + +import NavigateNextIcon from "@mui/icons-material/NavigateNext"; +import CheckIcon from "@mui/icons-material/Check"; +import { + useContext, + useEffect, + useState, +} from "react"; + +import { useRouter } from "next/router"; + +import LoadingButton from "component/LoadingButton"; +import Loading from "component/Loading"; +import CompletedCheckbox from "component/CompletedCheckbox"; +import FileUpload from "component/FileUpload"; +import { ElectionContext } from "context/ElectionContext"; +import { setElectionBallots, setElectionDefinition, setElectionVoters } from "requests/election"; + +interface ElectionProductionVotersFormProps { + election: Maybe; + onUpdateElection(election: Election): void; + onCancel(): void; + viewOnly?: boolean; +} + +export default function ElectionProductionVotersForm({ + election, + onUpdateElection, + onCancel, + viewOnly = false +}: ElectionProductionVotersFormProps) { + + const { loadElection, voterFileStatus: voterFileStatusInDB, updateVoterFileStatus } = useContext(ElectionContext); + + const [voterFileStatus, setVoterFileStatus] = useState<{ [x: string]: any }>(voterFileStatusInDB); + + const router = useRouter(); + + useEffect(() => { + setVoterFileStatus(voterFileStatusInDB) + }, [voterFileStatusInDB]); + + const saveNext = async () => { + router.push( + `/elections/${(election as Election)?.electionId}/${StepsRoutes.Review}` + ); + }; + + const formFields = ( + + + Production Voter Data + + + + Production Voter List + { + try { + if ((election as Election)?.electionId) { + setVoterFileStatus({ status: "uploading" }); + const resp = await setElectionVoters( + (election as Election)?.electionId, + file + ); + updateVoterFileStatus(resp.objectKey); + loadElection(); + } + return; + } catch (e: any) { + setVoterFileStatus({ + status: "error", + message: e?.data?.error_description, + }); + // onUpdateElection(data as Election); + console.log(e); + } + }} + /> + + {voterFileStatus.status === "error" && ( + + Error Processing File: {voterFileStatus.message} +

+ Please see: help information +

+
+ )} + {voterFileStatus.status === "uploading" && ( + + + + )} + {voterFileStatus.status === "started" && ( + + Voter File Processing + + )} +
+
+ + {/* Production Voter List Upload History + + Date + Action + */} + + + + Production Voter List Upload Checklist + + + {voterFileStatus.status === "complete" && ( + + Voter File Uploaded + + )} + + 0}> + {(election as Election)?.voterCount || 0} voters uploaded + + +
+ ); + + + const actions = ( + + + + + + } onClick={saveNext}> + Review + + + + ); + + + return ( + <> + {election && ( + + {formFields} + {!viewOnly && {actions}} + + )} + + ); +} diff --git a/admin-ui/src/component/election-steps/ElectionReview.tsx b/admin-ui/src/component/election-steps/ElectionReview.tsx new file mode 100644 index 0000000..d93dbcd --- /dev/null +++ b/admin-ui/src/component/election-steps/ElectionReview.tsx @@ -0,0 +1,74 @@ +import Button from "@mui/material/Button"; +import Grid from "@mui/material/Grid"; +import Typography from "@mui/material/Typography"; +import CheckIcon from "@mui/icons-material/Check"; +import ElectionCard from "component/ElectionCard"; +import LoadingButton from "component/LoadingButton"; +import { + Election, + Maybe, +} from "types"; +import router from "next/router"; +import Loading from "component/Loading"; + +interface ElectionReviewProps { + election: Maybe; + onCancel(): void; + viewOnly?: boolean; +} + +export default function ElectionReview({ + election, + onCancel, + viewOnly = false +}: ElectionReviewProps) { + + const disableOpenElection = !election?.votersSet || !election?.testComplete; + + const handleOpenElection = () => { + router.push( + `/elections/${(election as Election)?.electionId}/open-live` + ); + } + + const actions = ( + + + + + + + + + ); + + const formFields = ( + + + Review Your Election. + + + + + + ) + + return ( + <> + {election && ( + + {formFields} + {!viewOnly && {actions}} + + )} + + ); +} \ No newline at end of file diff --git a/admin-ui/src/component/election-steps/ElectionTestForm.tsx b/admin-ui/src/component/election-steps/ElectionTestForm.tsx new file mode 100644 index 0000000..5e549ff --- /dev/null +++ b/admin-ui/src/component/election-steps/ElectionTestForm.tsx @@ -0,0 +1,308 @@ +import Button from "@mui/material/Button"; +import Grid from "@mui/material/Grid"; +import Typography from "@mui/material/Typography"; +import CheckIcon from "@mui/icons-material/Check"; +import NavigateNextIcon from "@mui/icons-material/NavigateNext"; +import ConstructionIcon from "@mui/icons-material/Construction"; +import ElectionCard from "component/ElectionCard"; +import LoadingButton from "component/LoadingButton"; +import { + Election, + ElectionCreate, + ElectionStatus, + Maybe, + StepsRoutes, +} from "types"; +import router, { useRouter } from "next/router"; +import Loading from "component/Loading"; +import { useContext, useEffect, useState } from "react"; +import ConfirmationDialog from "component/ConfirmationDialog"; +import { setCurrentElection, setElectionTestComplete, setTestVoterFile } from "requests/election"; +import GI from "component/GI"; +import FileUpload from "component/FileUpload"; +import Box from "@mui/material/Box"; +import { Alert, Link } from "@mui/material"; +import CompletedCheckbox from "component/CompletedCheckbox"; +import useCurrentElection from "hooks/useCurrentElection"; +import { ElectionContext } from "context/ElectionContext"; + +interface ElectionTestFormProps { + election: Maybe; + onCancel(): void; + viewOnly?: boolean; +} + +export default function ElectionTestForm({ + election, + onCancel, + viewOnly = false +}: ElectionTestFormProps) { + + // const [data, setData] = useState>(election); + const [currentElection, reloadCurrentElection, loadingCurrentElection] = useCurrentElection(); + const { loadElection } = useContext(ElectionContext); + + const [testVoterFileStatus, setTestVoterFileStatus] = useState<{ + [x: string]: any; + }>( + //election?.testVotersFile ? { status: "started" } : {} + election?.testVotersFile ? { status: "started" } : {} + ); + const [currentElectionDialogOpen, setCurrentElectionDialogOpen] = useState(false); + + const [alertText, setAlertText] = useState(""); + + console.log('Election', election) + + const router = useRouter(); + + const setAlert = (text: string) => { + setAlertText(text); + setTimeout(() => setAlertText(""), 4000); + }; + + const messageError = (e: any) => { + setAlert( + e?.data?.error_description || + e?.data?.message || + e?.message || + JSON.stringify(e) + ); + }; + + const handleSetCurrentElection = async () => { + if (currentElection) { + setCurrentElectionDialogOpen(true); + } else { + await saveAsCurrentElection(); + } + }; + + const handleCurrentElectionDialogClose = async (confirmed: boolean) => { + + if (confirmed) { + await saveAsCurrentElection(); + } else { + setCurrentElectionDialogOpen(false); + } + + }; + + const saveAsCurrentElection = async () => { + if (election) { + try { + await setCurrentElection(election.electionId); + } catch (e) { + console.log("setCurrentElection error"); + console.log(e); + messageError(e); + } finally { + await loadElection(); + reloadCurrentElection(); + setCurrentElectionDialogOpen(false); + } + } + } + + const saveNext = () => { + router.push( + `/elections/${(election as Election)?.electionId}/${StepsRoutes.ProductionVoterData}` + ); + } + + const formFields = ( + + + Test Your Election + + + Upload Test Voter List + { + try { + if ((election as Election)?.electionId) { + setTestVoterFileStatus({ status: "uploading" }); + const resp = await setTestVoterFile( + (election as Election)?.electionId, + file + ); + // updateTestVoterFileStatus(resp.objectKey); + setTestVoterFileStatus({ status: "done" }); + loadElection(); + //setTestVoterFileUid(resp.objectKey); + } + return; + } catch (e: any) { + setTestVoterFileStatus({ + status: "error", + message: JSON.stringify(e?.data?.error_description), + }); + // onUpdateElection(data as Election); + console.log(e); + } + }} + /> + + {testVoterFileStatus.status === "error" && ( + + Error Processing File: {testVoterFileStatus.message} +

+ Please see: help information +

+
+ )} + {testVoterFileStatus.status === "uploading" && ( + + + + )} + {testVoterFileStatus.status === "started" && ( + + Voter File Processing + + )} +
+
+ + + Test Data Upload Checklist + + {testVoterFileStatus.status === "complete" && ( + + Voter File Uploaded + + )} + + 0}> + {(election as Election)?.testVoterCount || 0} test voters uploaded + + +
+ ); + + const actions = ( + + + + + + + + { + election && + currentElection && + currentElection?.electionId === election.electionId && + election?.electionStatus == ElectionStatus.inactive && + election?.testVotersSet && + !election?.testComplete ? ( + + + + + ) : ( + + { + election && + !loadingCurrentElection && currentElection && + currentElection?.electionId !== election.electionId && ( +

+ This election is not the current election and so cannot be set + to test mode. +

+ )} + + { + election && + (!election.electionStatus || + election.electionStatus === ElectionStatus.draft) && + (!currentElection || + (!loadingCurrentElection && currentElection && + currentElection?.electionId !== election?.electionId && + currentElection?.latMode !== 1 && + currentElection?.electionStatus !== ElectionStatus.open)) && ( + <> + + Set Current + + + + It will archive the election for {currentElection?.electionJurisdictionName} {currentElection?.electionName}. + + + + )} +
+ )} + + { + election && + currentElection && + currentElection?.electionId === election.electionId && + (election?.testCount ?? 0) >= 1 && + !election?.testComplete && ( + } + onClick={async () => { + await setElectionTestComplete((election as Election)?.electionId); + router.push("/dashboard"); + }} + > + Finalize Testing + + )} + +
+ ); + + + + return ( + <> + {election && ( + + {formFields} + + {alertText && {alertText}} + + {!viewOnly && {actions}} + + )} + + ); +} \ No newline at end of file diff --git a/admin-ui/src/context/ElectionContext.tsx b/admin-ui/src/context/ElectionContext.tsx index 3cca37e..e6bc0dc 100644 --- a/admin-ui/src/context/ElectionContext.tsx +++ b/admin-ui/src/context/ElectionContext.tsx @@ -1,17 +1,34 @@ import React, { createContext, ReactNode, useEffect, useState } from 'react'; -import { createElection, getElection, setElectionAttributes, setElectionConfigurations } from 'requests/election'; +import { createElection, getElection, getFileStatus, setElectionAttributes, setElectionConfigurations } from 'requests/election'; import { Election, ElectionCreate, Maybe } from 'types'; interface ElectionContextType { election: Maybe; - loadElection: (electionId: string) => void; + loadElection: () => void; updateElection: (data: Election) => void; + edfStatus: { [x: string]: any }, + updateEDFStatus: (edfUid: string) => void + ballotsStatus: { [x: string]: any }, + updateBallotsStatus: (ballotsUid: string) => void + voterFileStatus: { [x: string]: any }, + updateVoterFileStatus: (voterFileUid: string) => void, + testVoterFileStatus: { [x: string]: any }, + updateTestVoterFileStatus: (testVoterFileUid: string) => void + } export const ElectionContext = createContext({ election: null, - loadElection: (electionId: string):void => {}, - updateElection: (data: Election): void => {} + loadElection: ():void => {}, + updateElection: (data: Election): void => {}, + edfStatus: {}, + updateEDFStatus: (edfUid: string): void => {}, + ballotsStatus: {}, + updateBallotsStatus: (ballotsUid: string): void => {}, + voterFileStatus: {}, + updateVoterFileStatus: (voterFileUid: string): void => {}, + testVoterFileStatus: {}, + updateTestVoterFileStatus: (voterFileUid: string): void => {}, }); type ElectionProviderProps = { @@ -22,6 +39,19 @@ type ElectionProviderProps = { export const ElectionProvider = ({electionId, children }: ElectionProviderProps) => { const [election, setElection] = useState>(null); + const [edfStatus, setEDFStatus] = useState<{ [x: string]: any }>( + election?.electionDefinitionFile ? { status: "started" } : {} + ); + const [ballotsStatus, setBallotsStatus] = useState<{ [x: string]: any }>( + election?.ballotsFile ? { status: "started" } : {} + ); + const [voterFileStatus, setVoterFileStatus] = useState<{ [x: string]: any }>( + election?.votersFile ? { status: "started" } : {} + ); + const [testVoterFileStatus, setTestVoterFileStatus] = useState<{ [x: string]: any }>( + election?.testVotersFile ? { status: "started" } : {} + ); + useEffect(() => { if (electionId) { loadElection(); @@ -32,15 +62,85 @@ export const ElectionProvider = ({electionId, children }: ElectionProviderProps if (electionId) { const resp = await getElection(electionId); setElection(resp); + + resp?.electionDefinitionFile && setEDFStatus({ status: "started" }); + resp?.ballotsFile && setBallotsStatus({ status: "started" }); + resp?.votersFile && setVoterFileStatus({ status: "started" }); + resp?.testVotersFile && setTestVoterFileStatus({ status: "started" }); + + updateEDFStatus(resp.electionDefinitionFile || ''); + updateBallotsStatus(resp.ballotsFile || ''); + updateVoterFileStatus(resp.votersFile || ''); + updateVoterFileStatus(resp.testVotersFile || ''); } }; - const updateElection = async (data: Election ) => { + const updateElection = (data: Election ) => { data && setElection(data); } + const updateFileStatus = async (uid: string, type: string) => { + if (uid) { + const resp = await getFileStatus(uid); + switch (type) { + case 'edf': + setEDFStatus(resp); + break; + case 'ballot': + setBallotsStatus(resp); + break; + case 'voterFile': + setVoterFileStatus(resp); + break; + case 'testVoterFile': + setTestVoterFileStatus(resp); + break; + default: + break; + } + } + } + + const updateEDFStatus = async (edfUid: string) => { + if (edfUid) { + const resp = await updateFileStatus(edfUid, 'edf'); + } + }; + + const updateBallotsStatus = async (ballotsUid: string) => { + if (ballotsUid) { + const resp = await updateFileStatus(ballotsUid, 'ballot'); + } + }; + + const updateVoterFileStatus = async (voterFileUid: string) => { + if (voterFileUid) { + const resp = await updateFileStatus(voterFileUid, 'voterFile'); + } + }; + + const updateTestVoterFileStatus = async (voterFileUid: string) => { + if (voterFileUid) { + const resp = await updateFileStatus(voterFileUid, 'testVoterFile'); + } + }; + + const electionContextValue = { + election, + edfStatus, + ballotsStatus, + voterFileStatus, + testVoterFileStatus, + loadElection, + updateElection, + updateEDFStatus, + updateBallotsStatus, + updateVoterFileStatus, + updateTestVoterFileStatus + }; + return ( - + {children} ); diff --git a/admin-ui/src/hooks/useWarningIfUnsavedData.ts b/admin-ui/src/hooks/useWarningIfUnsavedData.ts index 8e5e01f..fce9dae 100644 --- a/admin-ui/src/hooks/useWarningIfUnsavedData.ts +++ b/admin-ui/src/hooks/useWarningIfUnsavedData.ts @@ -1,12 +1,14 @@ -import { Router } from "next/router" +import { Router, useRouter } from "next/router" import { useEffect } from "react" -export const useWarningIfUnsavedData = (unsavedChanges: boolean) => { - +export default function useWarningIfUnsavedData (unsavedChanges: boolean) { + const router = useRouter(); const confirmMessage = "Your changes have not been saved. Do you want to discard them and leave this page?"; - const handleRouteChangeStart = () => { - if (unsavedChanges) { + const handleRouteChangeStart = (url: string) => { + console.log('Router url' , url); + console.log('Router.pathname !== url ', router.asPath) + if (unsavedChanges && router.asPath !== url) { if (!window.confirm(confirmMessage)) { Router.events.emit('routeChangeError'); throw 'Route change cancelled. Ignore this error.'; @@ -28,6 +30,7 @@ export const useWarningIfUnsavedData = (unsavedChanges: boolean) => { window.removeEventListener('beforeunload', handleBeforeUnload); Router.events.off('routeChangeStart', handleRouteChangeStart); } + return () => { window.removeEventListener('beforeunload', handleBeforeUnload); Router.events.off('routeChangeStart', handleRouteChangeStart); diff --git a/admin-ui/src/layout/ElectionPageLayout.tsx b/admin-ui/src/layout/ElectionPageLayout.tsx index 389c244..1d3535c 100644 --- a/admin-ui/src/layout/ElectionPageLayout.tsx +++ b/admin-ui/src/layout/ElectionPageLayout.tsx @@ -6,54 +6,57 @@ import Typography from "@mui/material/Typography"; import { ElectionProvider } from "context/ElectionContext"; import { useRouter } from "next/router"; import { ReactNode, useState } from "react"; +import { ElectionSteps, ElectionViewQueryParam } from "types/election"; import LoggedInLayout from "./LoggedInLayout"; interface ElectionPageLayoutProps { - showStepper?: boolean; - step: number; + hideStepper?: boolean; + step?: number; title: string; electionId?: string; children: ReactNode; } export default function ElectionPageLayout({ - showStepper = false, - step, + hideStepper = false, + step = 0, electionId = "", title, children }: ElectionPageLayoutProps) { const router = useRouter(); const stepRouteBaseURL = `/elections/${electionId}`; - - const steps = [ - {label: "Election Name", route: `election-name`}, - {label: "Election Settings", route: `election-settings`}, - {label: "Upload EDF", route: `edf`}, - {label: "Upload Ballots", route: `ballots`}, - {label: "Test Election", route: `test`}, - {label: "Production Voter Data", route: `production-voter`}, - {label: "Review", route: `review`} - ]; + const { query } = router; + const isViewMode = query.hasOwnProperty(ElectionViewQueryParam); return ( - + {title} - {children} - - - {steps.map((electionStep, index) => ( - index}> - router.push(`${stepRouteBaseURL}/${electionStep.route}`)}> - {electionStep.label} - - - ))} - - + {children} + {!hideStepper && + + + {ElectionSteps.map((electionStep, index) => ( + index}> + { + router.push({ + pathname: `${stepRouteBaseURL}/${electionStep.route}`, + query: isViewMode ? ElectionViewQueryParam : undefined + }) + }} + > + {electionStep.label} + + + ))} + + + } ); diff --git a/admin-ui/src/types/election.ts b/admin-ui/src/types/election.ts index a09ea6f..d9100f5 100644 --- a/admin-ui/src/types/election.ts +++ b/admin-ui/src/types/election.ts @@ -93,10 +93,10 @@ export type BallotFile = { export enum StepsRoutes { ElectionName = "election-name", ElectionSettings = "election-settings", - UploadEDF = "edf", - UploadBallots = "ballots", - TestElection = "test", - ProductionVoterData = "production-voter", + UploadEDF = "upload-edf", + UploadBallots = "upload-ballots", + TestElection = "test-election", + ProductionVoterData = "production-voters", Review = "review" } @@ -108,4 +108,6 @@ export const ElectionSteps: { label: string; route: StepsRoutes }[] = [ { label: "Test Election", route: StepsRoutes.TestElection }, { label: "Production Voter Data", route: StepsRoutes.ProductionVoterData }, { label: "Review", route: StepsRoutes.Review } -]; \ No newline at end of file +]; + +export const ElectionViewQueryParam = 'view-mode'; \ No newline at end of file From 46412dfef248ee25fb660842da378e32857d83af Mon Sep 17 00:00:00 2001 From: Renu Date: Sun, 9 Apr 2023 15:52:18 +0530 Subject: [PATCH 11/13] add required field validation on step 1 --- .../election-steps/ElectionAttributesForm.tsx | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx b/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx index 2f74d9e..820162e 100644 --- a/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx +++ b/admin-ui/src/component/election-steps/ElectionAttributesForm.tsx @@ -123,15 +123,36 @@ export default function ElectionAttributesForm({ ); }; - // const handleCancel = () => { - // router.push("/dashboard"); - // }; + const validateData = ():boolean => { + const newData = { ...(data || {}) } as { [x: string]: any }; + const requiredArgs = [ + "electionName", + "electionJurisdictionName", + "electionDate", + "electionVotingStartDate", + "electionVotingEndDate", + ]; + let error = false; + + if ( newData ) { + const hasAllRequiredValues = requiredArgs.every((key) => newData?.hasOwnProperty(key) && newData[key] && /\s/.test(newData[key])); + error = !hasAllRequiredValues; + } else { + error = true; + } + + error && messageError({message: 'Missing required arguments'}) + return error; + } const saveNext = async () => { try { if (data) { - setHasUnsavedChanges(false); - await saveElection(data); + const invalidData = validateData(); + if (!invalidData) { + setHasUnsavedChanges(false); + await saveElection(data); + } } } catch (e) { messageError(e); @@ -162,7 +183,7 @@ export default function ElectionAttributesForm({ data={data} onChange={handleDataChange} name="electionJurisdictionName" - label="Enter Jurisdiction Name." + label="Enter Jurisdiction Name.*" placeholder="Jurisdiction Name Here" readOnly={viewOnly} /> @@ -173,7 +194,7 @@ export default function ElectionAttributesForm({ data={data} onChange={handleDataChange} name="electionName" - label="Enter Election Name." + label="Enter Election Name.*" placeholder="Election Name" readOnly={viewOnly} /> @@ -227,6 +248,7 @@ export default function ElectionAttributesForm({ label="Digital Absentee Voting End Date*" placeholder="E.g. 10/1/2022" {...endDateErrorProps} + readOnly={viewOnly} />
From 65eba84c04acc57652ac9f86b8a7eeab6afed120 Mon Sep 17 00:00:00 2001 From: Renu Date: Mon, 10 Apr 2023 21:44:42 +0530 Subject: [PATCH 12/13] refactor election steps into separate pages and few fixes --- admin-ui/package-lock.json | 1189 ++- admin-ui/package.json | 3 +- admin-ui/pages/_app.tsx | 7 +- admin-ui/pages/elections/[id]/[step].tsx | 4 +- admin-ui/src/component/DatePicker.tsx | 51 +- admin-ui/src/component/ElectionCard.tsx | 6 +- admin-ui/src/component/Input.tsx | 2 +- admin-ui/src/component/InputEnumSelect.tsx | 4 +- .../election-steps/ElectionAttributesForm.tsx | 39 +- .../ElectionBallotsFilesForm.tsx | 2 +- .../ElectionConfigurationsForm.tsx | 2 +- .../ElectionDefinitionFileForm.tsx | 4 +- .../election-steps/ElectionTestForm.tsx | 2 - admin-ui/src/context/ElectionContext.tsx | 42 +- admin-ui/src/hooks/useCurrentElection.ts | 15 +- admin-ui/src/hooks/useElections.ts | 17 +- admin-ui/src/requests/election.ts | 1 + admin-ui/src/utils/helpers.ts | 14 + admin-ui/yarn.lock | 8068 +++++++++-------- 19 files changed, 5342 insertions(+), 4130 deletions(-) create mode 100644 admin-ui/src/utils/helpers.ts diff --git a/admin-ui/package-lock.json b/admin-ui/package-lock.json index d861520..3001bab 100644 --- a/admin-ui/package-lock.json +++ b/admin-ui/package-lock.json @@ -17,6 +17,7 @@ "@mui/lab": "^5.0.0-alpha.55", "@mui/material": "^5.0.4", "@mui/styles": "^5.0.1", + "@mui/x-date-pickers": "^6.0.4", "axios": "^0.23.0", "date-fns": "^2.25.0", "html-loader": "^4.2.0", @@ -36,10 +37,23 @@ "node": "16.19.0" } }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", "dependencies": { "@babel/highlight": "^7.18.6" }, @@ -47,6 +61,223 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/compat-data": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", + "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", + "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.4", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.4", + "@babel/types": "^7.21.4", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "peer": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "peer": true + }, + "node_modules/@babel/generator": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", + "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "peer": true, + "dependencies": { + "@babel/types": "^7.21.4", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", + "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.21.4", + "@babel/helper-validator-option": "^7.21.0", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "peer": true + }, + "node_modules/@babel/helper-compilation-targets/node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "peer": true, + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "peer": true + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "peer": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "peer": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", @@ -58,6 +289,25 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", + "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-plugin-utils": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz", @@ -66,10 +316,65 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-identifier": { + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "peer": true, + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "peer": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "peer": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" + }, "engines": { "node": ">=6.9.0" } @@ -87,6 +392,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/parser": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", + "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", + "peer": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", @@ -102,11 +419,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz", - "integrity": "sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", "dependencies": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.13.11" }, "engines": { "node": ">=6.9.0" @@ -125,12 +442,80 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", + "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.4", + "@babel/types": "^7.21.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "peer": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "peer": true + }, "node_modules/@babel/types": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.8.tgz", - "integrity": "sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", + "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" }, "engines": { @@ -138,16 +523,16 @@ } }, "node_modules/@date-io/core": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.14.0.tgz", - "integrity": "sha512-qFN64hiFjmlDHJhu+9xMkdfDG2jLsggNxKXglnekUpXSq8faiqZgtHm2lsHCUuaPDTV6wuXHcCl8J1GQ5wLmPw==" + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.16.0.tgz", + "integrity": "sha512-DYmSzkr+jToahwWrsiRA2/pzMEtz9Bq1euJwoOuYwuwIYXnZFtHajY2E6a1VNVDc9jP8YUXK1BvnZH9mmT19Zg==" }, "node_modules/@date-io/date-fns": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.14.0.tgz", - "integrity": "sha512-4fJctdVyOd5cKIKGaWUM+s3MUXMuzkZaHuTY15PH70kU1YTMrCoauA7hgQVx9qj0ZEbGrH9VSPYJYnYro7nKiA==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.16.0.tgz", + "integrity": "sha512-bfm5FJjucqlrnQcXDVU5RD+nlGmL3iWgkHTq3uAZWVIuBu6dDmGa3m8a6zo2VQQpu8ambq9H22UyUpn7590joA==", "dependencies": { - "@date-io/core": "^2.14.0" + "@date-io/core": "^2.16.0" }, "peerDependencies": { "date-fns": "^2.0.0" @@ -158,12 +543,28 @@ } } }, + "node_modules/@date-io/date-fns-jalali": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/date-fns-jalali/-/date-fns-jalali-2.16.0.tgz", + "integrity": "sha512-MNVvGYwRiBydbvY7gvZM14W2kosIG29G1Ekw5qmYWOXkIIFngh6ZvV7/uVGDCW+gqlIeSz/XitZXA9n8RO0tJw==", + "dependencies": { + "@date-io/core": "^2.16.0" + }, + "peerDependencies": { + "date-fns-jalali": "^2.13.0-0" + }, + "peerDependenciesMeta": { + "date-fns-jalali": { + "optional": true + } + } + }, "node_modules/@date-io/dayjs": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-2.14.0.tgz", - "integrity": "sha512-4fRvNWaOh7AjvOyJ4h6FYMS7VHLQnIEeAV5ahv6sKYWx+1g1UwYup8h7+gPuoF+sW2hTScxi7PVaba2Jk/U8Og==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-2.16.0.tgz", + "integrity": "sha512-y5qKyX2j/HG3zMvIxTobYZRGnd1FUW2olZLS0vTj7bEkBQkjd2RO7/FEwDY03Z1geVGlXKnzIATEVBVaGzV4Iw==", "dependencies": { - "@date-io/core": "^2.14.0" + "@date-io/core": "^2.16.0" }, "peerDependencies": { "dayjs": "^1.8.17" @@ -174,15 +575,47 @@ } } }, + "node_modules/@date-io/hijri": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@date-io/hijri/-/hijri-2.16.1.tgz", + "integrity": "sha512-6BxY0mtnqj5cBiXluRs3uWN0mSJwGw0AB2ZxqtEHvBFoiSYEojW51AETnfPIWpdvDsBn+WAC7QrfBvQZnoyIkQ==", + "dependencies": { + "@date-io/moment": "^2.16.1" + }, + "peerDependencies": { + "moment-hijri": "^2.1.2" + }, + "peerDependenciesMeta": { + "moment-hijri": { + "optional": true + } + } + }, + "node_modules/@date-io/jalaali": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@date-io/jalaali/-/jalaali-2.16.1.tgz", + "integrity": "sha512-GLw87G/WJ1DNrQHW8p/LqkqAqTUSqBSRin0H1pRPwCccB5Fh7GT64sadjzEvjW56lPJ0aq2vp5yI2eIjZajfrw==", + "dependencies": { + "@date-io/moment": "^2.16.1" + }, + "peerDependencies": { + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0" + }, + "peerDependenciesMeta": { + "moment-jalaali": { + "optional": true + } + } + }, "node_modules/@date-io/luxon": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@date-io/luxon/-/luxon-2.14.0.tgz", - "integrity": "sha512-KmpBKkQFJ/YwZgVd0T3h+br/O0uL9ZdE7mn903VPAG2ZZncEmaUfUdYKFT7v7GyIKJ4KzCp379CRthEbxevEVg==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@date-io/luxon/-/luxon-2.16.1.tgz", + "integrity": "sha512-aeYp5K9PSHV28946pC+9UKUi/xMMYoaGelrpDibZSgHu2VWHXrr7zWLEr+pMPThSs5vt8Ei365PO+84pCm37WQ==", "dependencies": { - "@date-io/core": "^2.14.0" + "@date-io/core": "^2.16.0" }, "peerDependencies": { - "luxon": "^1.21.3 || ^2.x" + "luxon": "^1.21.3 || ^2.x || ^3.x" }, "peerDependenciesMeta": { "luxon": { @@ -191,11 +624,11 @@ } }, "node_modules/@date-io/moment": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.14.0.tgz", - "integrity": "sha512-VsoLXs94GsZ49ecWuvFbsa081zEv2xxG7d+izJsqGa2L8RPZLlwk27ANh87+SNnOUpp+qy2AoCAf0mx4XXhioA==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.16.1.tgz", + "integrity": "sha512-JkxldQxUqZBfZtsaCcCMkm/dmytdyq5pS1RxshCQ4fHhsvP5A7gSqPD22QbVXMcJydi3d3v1Y8BQdUKEuGACZQ==", "dependencies": { - "@date-io/core": "^2.14.0" + "@date-io/core": "^2.16.0" }, "peerDependencies": { "moment": "^2.24.0" @@ -228,6 +661,11 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@emotion/babel-plugin/node_modules/stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + }, "node_modules/@emotion/cache": { "version": "11.9.3", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.9.3.tgz", @@ -240,6 +678,11 @@ "stylis": "4.0.13" } }, + "node_modules/@emotion/cache/node_modules/stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + }, "node_modules/@emotion/css": { "version": "11.9.0", "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.9.0.tgz", @@ -661,6 +1104,53 @@ } } }, + "node_modules/@mui/lab/node_modules/@mui/x-date-pickers": { + "version": "5.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.0-alpha.1.tgz", + "integrity": "sha512-dLPkRiIn2Gr0momblxiOnIwrxn4SijVix+8e08mwAGWhiWcmWep1O9XTRDpZsjB0kjHYCf+kZjlRX4dxnj2acg==", + "dependencies": { + "@date-io/date-fns": "^2.11.0", + "@date-io/dayjs": "^2.11.0", + "@date-io/luxon": "^2.11.1", + "@date-io/moment": "^2.11.0", + "@mui/utils": "^5.6.0", + "clsx": "^1.1.1", + "prop-types": "^15.7.2", + "react-transition-group": "^4.4.2", + "rifm": "^0.12.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@mui/material": "^5.2.3", + "@mui/system": "^5.2.3", + "date-fns": "^2.25.0", + "dayjs": "^1.10.7", + "luxon": "^1.28.0 || ^2.0.0", + "moment": "^2.29.1", + "react": "^17.0.2 || ^18.0.0", + "react-dom": "^17.0.2 || ^18.0.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, "node_modules/@mui/lab/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -864,15 +1354,15 @@ } }, "node_modules/@mui/utils": { - "version": "5.8.6", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.8.6.tgz", - "integrity": "sha512-QM2Sd1xZo2jOt2Vz5Rmro+pi2FLJyiv4+OjxkUwXR3oUM65KSMAMLl/KNYU55s3W3DLRFP5MVwE4FhAbHseHAg==", + "version": "5.11.13", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.13.tgz", + "integrity": "sha512-5ltA58MM9euOuUcnvwFJqpLdEugc9XFsRR8Gt4zZNb31XzMfSKJPR4eumulyhsOTK1rWf7K4D63NKFPfX0AxqA==", "dependencies": { - "@babel/runtime": "^7.17.2", + "@babel/runtime": "^7.21.0", "@types/prop-types": "^15.7.5", "@types/react-is": "^16.7.1 || ^17.0.0", "prop-types": "^15.8.1", - "react-is": "^17.0.2" + "react-is": "^18.2.0" }, "engines": { "node": ">=12.0.0" @@ -886,46 +1376,65 @@ } }, "node_modules/@mui/utils/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/@mui/x-date-pickers": { - "version": "5.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.0-alpha.1.tgz", - "integrity": "sha512-dLPkRiIn2Gr0momblxiOnIwrxn4SijVix+8e08mwAGWhiWcmWep1O9XTRDpZsjB0kjHYCf+kZjlRX4dxnj2acg==", - "dependencies": { - "@date-io/date-fns": "^2.11.0", - "@date-io/dayjs": "^2.11.0", - "@date-io/luxon": "^2.11.1", - "@date-io/moment": "^2.11.0", - "@mui/utils": "^5.6.0", - "clsx": "^1.1.1", - "prop-types": "^15.7.2", - "react-transition-group": "^4.4.2", - "rifm": "^0.12.1" + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.0.4.tgz", + "integrity": "sha512-blh91rqgnr8mdm+bn5vdde7fQII0gUDmeOMttKrMLv6P6OxkQXdz5aLSroXUpPTAAbX53zciFW/UG5Ynxqxtpw==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@date-io/core": "^2.16.0", + "@date-io/date-fns": "^2.16.0", + "@date-io/date-fns-jalali": "^2.16.0", + "@date-io/dayjs": "^2.16.0", + "@date-io/hijri": "^2.16.1", + "@date-io/jalaali": "^2.16.1", + "@date-io/luxon": "^2.16.1", + "@date-io/moment": "^2.16.1", + "@mui/utils": "^5.11.13", + "@types/react-transition-group": "^4.4.5", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mui" }, "peerDependencies": { - "@mui/material": "^5.2.3", - "@mui/system": "^5.2.3", + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.4.1", + "@mui/system": "^5.4.1", "date-fns": "^2.25.0", + "date-fns-jalali": "^2.13.0-0", "dayjs": "^1.10.7", - "luxon": "^1.28.0 || ^2.0.0", - "moment": "^2.29.1", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0", "react": "^17.0.2 || ^18.0.0", "react-dom": "^17.0.2 || ^18.0.0" }, "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, "date-fns": { "optional": true }, + "date-fns-jalali": { + "optional": true + }, "dayjs": { "optional": true }, @@ -934,6 +1443,12 @@ }, "moment": { "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true } } }, @@ -2192,9 +2707,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001364", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001364.tgz", - "integrity": "sha512-9O0xzV3wVyX0SlegIQ6knz+okhBB5pE0PC40MNdwcipjwpxoUEHL24uJ+gG42cgklPjfO5ZjZPme9FTSN3QT2Q==", + "version": "1.0.30001476", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001476.tgz", + "integrity": "sha512-JmpktFppVSvyUN4gsLS0bShY2L9ZUslHLE72vgemBkS43JD2fOvKTKs+GtRwuxrtRGnwJFW0ye7kWRRlLJS9vQ==", "funding": [ { "type": "opencollective", @@ -2203,6 +2718,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -2687,9 +3206,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.185", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.185.tgz", - "integrity": "sha512-9kV/isoOGpKkBt04yYNaSWIBn3187Q5VZRtoReq8oz5NY/A4XmU6cAoqgQlDp7kKJCZMRjWZ8nsQyxfpFHvfyw==" + "version": "1.4.356", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.356.tgz", + "integrity": "sha512-nEftV1dRX3omlxAj42FwqRZT0i4xd2dIg39sog/CnCJeCcL1TRd2Uh0i9Oebgv8Ou0vzTPw++xc+Z20jzS2B6A==" }, "node_modules/elliptic": { "version": "6.5.4", @@ -3761,6 +4280,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-intrinsic": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", @@ -4564,6 +5092,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -5649,6 +6189,12 @@ "node": ">=0.12" } }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "peer": true + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -5951,9 +6497,9 @@ } }, "node_modules/react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -5988,9 +6534,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", @@ -6652,16 +7198,11 @@ "node": ">= 8" } }, - "node_modules/styled-jsx/node_modules/stylis": { + "node_modules/stylis": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==" }, - "node_modules/stylis": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", - "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" - }, "node_modules/stylis-rule-sheet": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", @@ -7333,14 +7874,171 @@ } }, "dependencies": { + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "peer": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", "requires": { "@babel/highlight": "^7.18.6" } }, + "@babel/compat-data": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", + "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==", + "peer": true + }, + "@babel/core": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", + "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", + "peer": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.4", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.4", + "@babel/types": "^7.21.4", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "peer": true, + "requires": { + "ms": "2.1.2" + } + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "peer": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "peer": true + } + } + }, + "@babel/generator": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", + "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "peer": true, + "requires": { + "@babel/types": "^7.21.4", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", + "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", + "peer": true, + "requires": { + "@babel/compat-data": "^7.21.4", + "@babel/helper-validator-option": "^7.21.0", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "dependencies": { + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "peer": true, + "requires": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "peer": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "peer": true + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "peer": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "peer": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "peer": true + }, + "@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "peer": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "peer": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, "@babel/helper-module-imports": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", @@ -7349,15 +8047,71 @@ "@babel/types": "^7.18.6" } }, + "@babel/helper-module-transforms": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", + "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "peer": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2" + } + }, "@babel/helper-plugin-utils": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz", "integrity": "sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg==" }, - "@babel/helper-validator-identifier": { + "@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "peer": true, + "requires": { + "@babel/types": "^7.20.2" + } + }, + "@babel/helper-split-export-declaration": { "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==" + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "peer": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "peer": true + }, + "@babel/helpers": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "peer": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" + } }, "@babel/highlight": { "version": "7.18.6", @@ -7369,6 +8123,12 @@ "js-tokens": "^4.0.0" } }, + "@babel/parser": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", + "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", + "peer": true + }, "@babel/plugin-syntax-jsx": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", @@ -7378,11 +8138,11 @@ } }, "@babel/runtime": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz", - "integrity": "sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", "requires": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.13.11" } }, "@babel/runtime-corejs3": { @@ -7395,50 +8155,127 @@ "regenerator-runtime": "^0.13.4" } }, + "@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "peer": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/traverse": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", + "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", + "peer": true, + "requires": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.4", + "@babel/types": "^7.21.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "peer": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "peer": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "peer": true + } + } + }, "@babel/types": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.8.tgz", - "integrity": "sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", + "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", "requires": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" } }, "@date-io/core": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.14.0.tgz", - "integrity": "sha512-qFN64hiFjmlDHJhu+9xMkdfDG2jLsggNxKXglnekUpXSq8faiqZgtHm2lsHCUuaPDTV6wuXHcCl8J1GQ5wLmPw==" + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.16.0.tgz", + "integrity": "sha512-DYmSzkr+jToahwWrsiRA2/pzMEtz9Bq1euJwoOuYwuwIYXnZFtHajY2E6a1VNVDc9jP8YUXK1BvnZH9mmT19Zg==" }, "@date-io/date-fns": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.14.0.tgz", - "integrity": "sha512-4fJctdVyOd5cKIKGaWUM+s3MUXMuzkZaHuTY15PH70kU1YTMrCoauA7hgQVx9qj0ZEbGrH9VSPYJYnYro7nKiA==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.16.0.tgz", + "integrity": "sha512-bfm5FJjucqlrnQcXDVU5RD+nlGmL3iWgkHTq3uAZWVIuBu6dDmGa3m8a6zo2VQQpu8ambq9H22UyUpn7590joA==", + "requires": { + "@date-io/core": "^2.16.0" + } + }, + "@date-io/date-fns-jalali": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/date-fns-jalali/-/date-fns-jalali-2.16.0.tgz", + "integrity": "sha512-MNVvGYwRiBydbvY7gvZM14W2kosIG29G1Ekw5qmYWOXkIIFngh6ZvV7/uVGDCW+gqlIeSz/XitZXA9n8RO0tJw==", "requires": { - "@date-io/core": "^2.14.0" + "@date-io/core": "^2.16.0" } }, "@date-io/dayjs": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-2.14.0.tgz", - "integrity": "sha512-4fRvNWaOh7AjvOyJ4h6FYMS7VHLQnIEeAV5ahv6sKYWx+1g1UwYup8h7+gPuoF+sW2hTScxi7PVaba2Jk/U8Og==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-2.16.0.tgz", + "integrity": "sha512-y5qKyX2j/HG3zMvIxTobYZRGnd1FUW2olZLS0vTj7bEkBQkjd2RO7/FEwDY03Z1geVGlXKnzIATEVBVaGzV4Iw==", + "requires": { + "@date-io/core": "^2.16.0" + } + }, + "@date-io/hijri": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@date-io/hijri/-/hijri-2.16.1.tgz", + "integrity": "sha512-6BxY0mtnqj5cBiXluRs3uWN0mSJwGw0AB2ZxqtEHvBFoiSYEojW51AETnfPIWpdvDsBn+WAC7QrfBvQZnoyIkQ==", "requires": { - "@date-io/core": "^2.14.0" + "@date-io/moment": "^2.16.1" + } + }, + "@date-io/jalaali": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@date-io/jalaali/-/jalaali-2.16.1.tgz", + "integrity": "sha512-GLw87G/WJ1DNrQHW8p/LqkqAqTUSqBSRin0H1pRPwCccB5Fh7GT64sadjzEvjW56lPJ0aq2vp5yI2eIjZajfrw==", + "requires": { + "@date-io/moment": "^2.16.1" } }, "@date-io/luxon": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@date-io/luxon/-/luxon-2.14.0.tgz", - "integrity": "sha512-KmpBKkQFJ/YwZgVd0T3h+br/O0uL9ZdE7mn903VPAG2ZZncEmaUfUdYKFT7v7GyIKJ4KzCp379CRthEbxevEVg==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@date-io/luxon/-/luxon-2.16.1.tgz", + "integrity": "sha512-aeYp5K9PSHV28946pC+9UKUi/xMMYoaGelrpDibZSgHu2VWHXrr7zWLEr+pMPThSs5vt8Ei365PO+84pCm37WQ==", "requires": { - "@date-io/core": "^2.14.0" + "@date-io/core": "^2.16.0" } }, "@date-io/moment": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.14.0.tgz", - "integrity": "sha512-VsoLXs94GsZ49ecWuvFbsa081zEv2xxG7d+izJsqGa2L8RPZLlwk27ANh87+SNnOUpp+qy2AoCAf0mx4XXhioA==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.16.1.tgz", + "integrity": "sha512-JkxldQxUqZBfZtsaCcCMkm/dmytdyq5pS1RxshCQ4fHhsvP5A7gSqPD22QbVXMcJydi3d3v1Y8BQdUKEuGACZQ==", "requires": { - "@date-io/core": "^2.14.0" + "@date-io/core": "^2.16.0" } }, "@emotion/babel-plugin": { @@ -7458,6 +8295,13 @@ "find-root": "^1.1.0", "source-map": "^0.5.7", "stylis": "4.0.13" + }, + "dependencies": { + "stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + } } }, "@emotion/cache": { @@ -7470,6 +8314,13 @@ "@emotion/utils": "^1.0.0", "@emotion/weak-memoize": "^0.2.5", "stylis": "4.0.13" + }, + "dependencies": { + "stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + } } }, "@emotion/css": { @@ -7751,6 +8602,22 @@ "rifm": "^0.12.1" }, "dependencies": { + "@mui/x-date-pickers": { + "version": "5.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.0-alpha.1.tgz", + "integrity": "sha512-dLPkRiIn2Gr0momblxiOnIwrxn4SijVix+8e08mwAGWhiWcmWep1O9XTRDpZsjB0kjHYCf+kZjlRX4dxnj2acg==", + "requires": { + "@date-io/date-fns": "^2.11.0", + "@date-io/dayjs": "^2.11.0", + "@date-io/luxon": "^2.11.1", + "@date-io/moment": "^2.11.0", + "@mui/utils": "^5.6.0", + "clsx": "^1.1.1", + "prop-types": "^15.7.2", + "react-transition-group": "^4.4.2", + "rifm": "^0.12.1" + } + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -7850,38 +8717,43 @@ "requires": {} }, "@mui/utils": { - "version": "5.8.6", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.8.6.tgz", - "integrity": "sha512-QM2Sd1xZo2jOt2Vz5Rmro+pi2FLJyiv4+OjxkUwXR3oUM65KSMAMLl/KNYU55s3W3DLRFP5MVwE4FhAbHseHAg==", + "version": "5.11.13", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.13.tgz", + "integrity": "sha512-5ltA58MM9euOuUcnvwFJqpLdEugc9XFsRR8Gt4zZNb31XzMfSKJPR4eumulyhsOTK1rWf7K4D63NKFPfX0AxqA==", "requires": { - "@babel/runtime": "^7.17.2", + "@babel/runtime": "^7.21.0", "@types/prop-types": "^15.7.5", "@types/react-is": "^16.7.1 || ^17.0.0", "prop-types": "^15.8.1", - "react-is": "^17.0.2" + "react-is": "^18.2.0" }, "dependencies": { "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" } } }, "@mui/x-date-pickers": { - "version": "5.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.0-alpha.1.tgz", - "integrity": "sha512-dLPkRiIn2Gr0momblxiOnIwrxn4SijVix+8e08mwAGWhiWcmWep1O9XTRDpZsjB0kjHYCf+kZjlRX4dxnj2acg==", - "requires": { - "@date-io/date-fns": "^2.11.0", - "@date-io/dayjs": "^2.11.0", - "@date-io/luxon": "^2.11.1", - "@date-io/moment": "^2.11.0", - "@mui/utils": "^5.6.0", - "clsx": "^1.1.1", - "prop-types": "^15.7.2", - "react-transition-group": "^4.4.2", - "rifm": "^0.12.1" + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.0.4.tgz", + "integrity": "sha512-blh91rqgnr8mdm+bn5vdde7fQII0gUDmeOMttKrMLv6P6OxkQXdz5aLSroXUpPTAAbX53zciFW/UG5Ynxqxtpw==", + "requires": { + "@babel/runtime": "^7.21.0", + "@date-io/core": "^2.16.0", + "@date-io/date-fns": "^2.16.0", + "@date-io/date-fns-jalali": "^2.16.0", + "@date-io/dayjs": "^2.16.0", + "@date-io/hijri": "^2.16.1", + "@date-io/jalaali": "^2.16.1", + "@date-io/luxon": "^2.16.1", + "@date-io/moment": "^2.16.1", + "@mui/utils": "^5.11.13", + "@types/react-transition-group": "^4.4.5", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" } }, "@napi-rs/triples": { @@ -8864,9 +9736,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001364", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001364.tgz", - "integrity": "sha512-9O0xzV3wVyX0SlegIQ6knz+okhBB5pE0PC40MNdwcipjwpxoUEHL24uJ+gG42cgklPjfO5ZjZPme9FTSN3QT2Q==" + "version": "1.0.30001476", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001476.tgz", + "integrity": "sha512-JmpktFppVSvyUN4gsLS0bShY2L9ZUslHLE72vgemBkS43JD2fOvKTKs+GtRwuxrtRGnwJFW0ye7kWRRlLJS9vQ==" }, "chalk": { "version": "2.4.2", @@ -9276,9 +10148,9 @@ } }, "electron-to-chromium": { - "version": "1.4.185", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.185.tgz", - "integrity": "sha512-9kV/isoOGpKkBt04yYNaSWIBn3187Q5VZRtoReq8oz5NY/A4XmU6cAoqgQlDp7kKJCZMRjWZ8nsQyxfpFHvfyw==" + "version": "1.4.356", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.356.tgz", + "integrity": "sha512-nEftV1dRX3omlxAj42FwqRZT0i4xd2dIg39sog/CnCJeCcL1TRd2Uh0i9Oebgv8Ou0vzTPw++xc+Z20jzS2B6A==" }, "elliptic": { "version": "6.5.4", @@ -10103,6 +10975,12 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "peer": true + }, "get-intrinsic": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", @@ -10654,6 +11532,12 @@ "esprima": "^4.0.0" } }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "peer": true + }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -11542,6 +12426,12 @@ "sha.js": "^2.4.8" } }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "peer": true + }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -11761,9 +12651,9 @@ } }, "react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "requires": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -11791,9 +12681,9 @@ } }, "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "regexp.prototype.flags": { "version": "1.4.3", @@ -12272,18 +13162,13 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" - }, - "stylis": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", - "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==" } } }, "stylis": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", - "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", + "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==" }, "stylis-rule-sheet": { "version": "0.0.10", diff --git a/admin-ui/package.json b/admin-ui/package.json index 6858357..597998c 100644 --- a/admin-ui/package.json +++ b/admin-ui/package.json @@ -22,6 +22,7 @@ "@mui/lab": "^5.0.0-alpha.55", "@mui/material": "^5.0.4", "@mui/styles": "^5.0.1", + "@mui/x-date-pickers": "^6.0.4", "axios": "^0.23.0", "date-fns": "^2.25.0", "html-loader": "^4.2.0", @@ -37,4 +38,4 @@ "eslint-config-next": "11.1.2", "typescript": "4.4.4" } -} \ No newline at end of file +} diff --git a/admin-ui/pages/_app.tsx b/admin-ui/pages/_app.tsx index 277824a..d05ee57 100644 --- a/admin-ui/pages/_app.tsx +++ b/admin-ui/pages/_app.tsx @@ -3,8 +3,11 @@ import type { AppProps } from "next/app"; import CssBaseline from "@mui/material/CssBaseline"; import { ThemeProvider } from "@mui/material/styles"; -import AdapterDateFns from "@mui/lab/AdapterDateFns"; -import LocalizationProvider from "@mui/lab/LocalizationProvider"; +// import AdapterDateFns from "@mui/lab/AdapterDateFns"; +// import LocalizationProvider from "@mui/lab/LocalizationProvider"; + +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns' +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider' import UserContext, { UserContextType } from "context/UserContext"; import { useEffect, useState } from "react"; diff --git a/admin-ui/pages/elections/[id]/[step].tsx b/admin-ui/pages/elections/[id]/[step].tsx index ed4eebc..b1441c7 100644 --- a/admin-ui/pages/elections/[id]/[step].tsx +++ b/admin-ui/pages/elections/[id]/[step].tsx @@ -13,12 +13,12 @@ const EditElectionName: NextPage = () => { const { query } = router; const { id, step } = query; - const electionId = Array.isArray(id) ? id[0] : id; + const electionId = Array.isArray(id) ? id[0] : id || ''; const stepName = Array.isArray(step) ? step[0] : step || ''; const viewOnly:boolean = query.hasOwnProperty(ElectionViewQueryParam); const activeStepIndex = stepName ? Object.values(StepsRoutes).indexOf(stepName as StepsRoutes) : 0; const pageTitle = viewOnly ? 'View Election' : 'Update ELection'; - console.log('pageTitle', pageTitle); + return ( diff --git a/admin-ui/src/component/DatePicker.tsx b/admin-ui/src/component/DatePicker.tsx index 6d87039..3f1565d 100644 --- a/admin-ui/src/component/DatePicker.tsx +++ b/admin-ui/src/component/DatePicker.tsx @@ -1,15 +1,15 @@ -import MUIDatePicker from '@mui/lab/DatePicker'; -import InputBase from '@mui/material/InputBase'; -import InputLabel from '@mui/material/InputLabel'; import { Maybe } from 'types'; -import Input, { InputProps } from './Input'; +import { DatePicker as MUIDatePicker } from '@mui/x-date-pickers/DatePicker'; +import { useState } from 'react'; interface DatePickerProps { name: string label: string placeholder?: string data?: Maybe<{[x: string]: any}> + readOnly?: boolean, onChange?: (name: string, value: Date | null) => void + [key: string]: any; } export default function DatePicker({ @@ -18,29 +18,38 @@ export default function DatePicker({ placeholder, onChange, data, + readOnly = false, ...props }: DatePickerProps ) { - const value = data && data[name] || "a"; - - //console.log(data, name); - return { - console.log('hi') - onChange && onChange(name, date); - }} + const value = data && data[name] && new Date(data[name]) || null; + const [open, setOpen] = useState(false); + + return { - const inputParams = { - ...params, - ...props - } as InputProps - return + onChange={(newValue) => onChange && onChange(name, newValue)} + onClose={() => setOpen(false)} + onOpen={() => setOpen(true)} + slotProps={{ + textField: { + size: 'small', + helperText: props.helperText, + error: props.error, + inputProps: { + onClick: () => setOpen(true) + } + + }, + actionBar: { + actions: ['clear'], + } }} + disablePast + readOnly={readOnly} /> + } diff --git a/admin-ui/src/component/ElectionCard.tsx b/admin-ui/src/component/ElectionCard.tsx index 3a4e80f..9c0b688 100644 --- a/admin-ui/src/component/ElectionCard.tsx +++ b/admin-ui/src/component/ElectionCard.tsx @@ -118,7 +118,7 @@ export default function ElectionCard({ )} {election?.votersSet && election?.electionStatus === ElectionStatus.inactive && ( - +