11import { useState , useMemo } from 'react' ;
22import { useNavigate } from "react-router" ;
3- import { PrimaryButton } from '../../styles.js' ;
4- import { Box , Breakpoint , Paper , Typography , useMediaQuery } from '@mui/material' ;
3+ import { PrimaryButton , UtilityButton } from '../../styles.js' ;
4+ import { Box , Paper , TextField , Typography } from '@mui/material' ;
55import { usePostElection } from '~/hooks/useAPI' ;
66import { setCookie , useCookie } from '~/hooks/useCookie' ;
77import { NewElection } from '@equal-vote/star-vote-shared/domain_model/Election' ;
88import { setVoterAuthenticationMode } from '@equal-vote/star-vote-shared/domain_model/VoterAuthenticationMode' ;
99import { makeUniqueIDSync , makeID , ID_PREFIXES , ID_LENGTHS } from '@equal-vote/star-vote-shared/utils/makeID' ;
1010
11- import { hashString , scrollToElement , StringObject , TransitionBox , useSubstitutedTranslation } from '../../util.js' ;
11+ import { hashString , TransitionBox , useSubstitutedTranslation } from '../../util.js' ;
1212import useAuthSession from '../../AuthSessionContextProvider.js' ;
1313import RaceForm from '../Races/RaceForm.js' ;
1414import useConfirm from '../../ConfirmationDialogProvider.js' ;
15- import WizardExtra from './WizardExtra.js' ;
16- import { ElectionContextProvider } from '../../ElectionContextProvider.js' ;
15+ import useElection , { ElectionContextProvider } from '../../ElectionContextProvider.js' ;
1716import WizardBasics from './WizardBasics.js' ;
18- import { useTheme } from '@mui/material' ;
1917
2018export const makeDefaultElection = ( ) => {
2119 const ids = [ ] ;
2220 for ( let i = 0 ; i < 1 ; i ++ ) {
2321 ids . push ( makeUniqueIDSync (
24- ID_PREFIXES . CANDIDATE ,
22+ ID_PREFIXES . CANDIDATE ,
2523 ID_LENGTHS . CANDIDATE ,
2624 ( id : string ) => ids . includes ( id )
2725 ) ) ;
@@ -34,7 +32,7 @@ export const makeDefaultElection = () => {
3432 owner_id : '0' ,
3533 is_public : false ,
3634 ballot_source : 'live_election' ,
37- races : [ {
35+ races : [ {
3836 title : '' ,
3937 race_id : '0' ,
4038 num_winners : undefined ,
@@ -46,7 +44,7 @@ export const makeDefaultElection = () => {
4644 precincts : undefined ,
4745 } ] ,
4846 settings : {
49- voter_access : undefined , // the wizard is responsible for setting this one
47+ voter_access : undefined , // onCustomize is responsible for setting this
5048 voter_authentication : {
5149 voter_id : true ,
5250 } ,
@@ -60,13 +58,55 @@ export const makeDefaultElection = () => {
6058 } as NewElection
6159} ;
6260
61+ const MultiRaceTitleSection = ( { onCustomize } : { onCustomize : ( election : NewElection ) => Promise < void > } ) => {
62+ const { election, updateElection, t } = useElection ( ) ;
63+ const [ showDescription , setShowDescription ] = useState ( false ) ;
64+
65+ return (
66+ < Box sx = { { textAlign : 'left' , pl : 1 } } >
67+ < Typography variant = 'h6' > { t ( 'election_details.title' ) } </ Typography >
68+ < TextField
69+ required
70+ label = { t ( 'election_details.title' ) }
71+ value = { election . title }
72+ fullWidth
73+ sx = { { mt : 1 , mb : 1 , boxShadow : 2 } }
74+ onChange = { ( e ) => updateElection ( el => { el . title = e . target . value } ) }
75+ slotProps = { { htmlInput : { 'aria-label' : 'Title' } } }
76+ />
77+ < UtilityButton onClick = { ( ) => setShowDescription ( d => ! d ) } >
78+ { showDescription ? '-' : '+' } { t ( 'wizard.description_title' ) }
79+ </ UtilityButton >
80+ { showDescription && (
81+ < TextField
82+ multiline
83+ fullWidth
84+ label = "Description"
85+ value = { election . description ?? '' }
86+ minRows = { 3 }
87+ sx = { { mt : 1 , mb : 1 , boxShadow : 2 } }
88+ onChange = { ( e ) => updateElection ( el => { el . description = e . target . value } ) }
89+ />
90+ ) }
91+ < Typography sx = { { mt : 1 } } > { t ( 'wizard.add_races_later' ) } </ Typography >
92+ < Box sx = { { mt : 3 , display : 'flex' , flexDirection : 'row' , justifyContent : 'flex-end' , gap : 1 } } >
93+ < PrimaryButton
94+ disabled = { ! election . title . trim ( ) }
95+ onClick = { ( ) => onCustomize ( election ) }
96+ >
97+ Next
98+ </ PrimaryButton >
99+ </ Box >
100+ </ Box >
101+ ) ;
102+ } ;
103+
63104const Wizard = ( ) => {
64105 const authSession = useAuthSession ( ) ;
65106 const defaultTempId = useMemo ( ( ) => makeID ( ID_PREFIXES . VOTER , ID_LENGTHS . VOTER ) , [ ] ) ;
66107 const [ tempID ] = useCookie ( 'temp_id' , defaultTempId ) ;
67108 const navigate = useNavigate ( )
68- const [ page , setPage ] = useState ( 0 ) ;
69- const { isPending, makeRequest : postElection } = usePostElection ( )
109+ const { makeRequest : postElection } = usePostElection ( )
70110 const [ election , setElection ] = useState < NewElection > ( makeDefaultElection ( ) )
71111 const [ multiRace , setMultiRace ] = useState ( undefined ) ;
72112
@@ -97,15 +137,13 @@ const Wizard = () => {
97137 navigate ( `/${ newElection . election . election_id } ${ subPage } ` )
98138 }
99139
100- const theme = useTheme ( ) ;
101-
102- const width : StringObject = { xs : '300px' , sm : '500px' } ;
103- const getWidth = ( ) => {
104- const keys : Breakpoint [ ] = [ 'sm' , 'xs' ] ; // biggest to smallest, must match width keys
105- // NOTE: I'm precomputing ups so that we don't get an error for variable number of hooks
106- const ups = keys . map ( key => useMediaQuery ( theme . breakpoints . up ( key ) , { noSsr : true } ) ) ;
107- return Number ( width [ keys . find ( ( _ , i ) => ups [ i ] ) ] . replace ( 'px' , '' ) ) ;
108- }
140+ const onCustomize = async ( electionToSubmit : NewElection ) => {
141+ const finalElection = {
142+ ...electionToSubmit ,
143+ settings : setVoterAuthenticationMode ( electionToSubmit . settings , 'open_unique_cookie' ) ,
144+ } ;
145+ await onAddElection ( finalElection , '/admin/build_ballot' ) ;
146+ } ;
109147
110148 const onNext = async ( editedRace ) => {
111149 const updatedElection = {
@@ -114,23 +152,25 @@ const Wizard = () => {
114152 title : editedRace . title ,
115153 description : editedRace . description ,
116154 }
117- const confirmed = await confirm ( t ( 'wizard.publish_confirm' ) ) ;
155+ const confirmed = await confirm ( { ...t ( 'wizard.publish_confirm' ) , dismissable : true } ) ;
156+ if ( confirmed === null ) {
157+ return ; // dialog dismissed — stay on wizard with inputs intact
158+ }
118159 if ( confirmed ) {
119160 onAddElection ( { ...updatedElection , owner_id : null , state : 'finalized' , settings : setVoterAuthenticationMode ( updatedElection . settings , 'open_unique_cookie' ) } , '/' )
120- } else {
121- scrollToElement ( document . querySelector ( '.wizard' ) ) ;
122- setElection ( updatedElection )
123- setPage ( 1 ) ;
161+ } else {
162+ await onCustomize ( updatedElection ) ;
124163 }
125164 }
126165
166+ const width = { xs : '300px' , sm : '500px' } ;
167+
127168 const pageSX = {
128169 display : 'flex' ,
129170 gap : 0 ,
130171 width : width ,
131172 flexDirection : 'column' ,
132173 textAlign : 'center' ,
133- //backgroundColor: //'lightShade.main',
134174 padding : 3 ,
135175 borderRadius : '20px' ,
136176 minWidth : { xs : '0px' , md : '400px' } ,
@@ -142,32 +182,17 @@ const Wizard = () => {
142182
143183 return < ElectionContextProvider id = { undefined } localElection = { election } setLocalElection = { setElection } >
144184 < Paper className = 'wizard' elevation = { 5 } sx = { {
145- //maxWidth: '613px',
146185 width : width ,
147186 margin : 'auto' ,
148187 overflow : 'clip' ,
149188 } } >
150- < Box
151- sx = { {
152- position : 'relative' ,
153- width : `${ getWidth ( ) * 2 } px` ,
154- left : `-${ page * getWidth ( ) } px` ,
155- transition : 'left 1s' ,
156- display : 'flex' ,
157- flexDirection : 'row' ,
158- } }
159- >
160- < Box sx = { pageSX } >
161- < Typography variant = 'h5' sx = { { color : 'lightShade.contrastText' } } > { t ( 'wizard.title' ) } </ Typography >
162- < WizardBasics multiRace = { multiRace } setMultiRace = { setMultiRace } />
163- < Box sx = { { position : 'relative' } } >
164- < TransitionBox absolute enabled = { multiRace === true } sx = { { textAlign : 'left' , pl : 1 } } >
165- { t ( 'wizard.add_races_later' ) }
166- < Box sx = { { mt : 3 , display : "flex" , flexDirection : "row" , justifyContent : "flex-end" , gap : 1 } } >
167- < PrimaryButton onClick = { ( ) => setPage ( 1 ) } > Next</ PrimaryButton >
168- </ Box >
169- </ TransitionBox >
170- </ Box >
189+ < Box sx = { pageSX } >
190+ < Typography variant = 'h5' color = { 'lightShade.contrastText' } > { t ( 'wizard.title' ) } </ Typography >
191+ < WizardBasics multiRace = { multiRace } setMultiRace = { setMultiRace } />
192+ < Box sx = { { position : 'relative' } } >
193+ < TransitionBox absolute enabled = { multiRace === true } >
194+ < MultiRaceTitleSection onCustomize = { onCustomize } />
195+ </ TransitionBox >
171196 < TransitionBox enabled = { multiRace === false } >
172197 < RaceForm
173198 raceIndex = { 0 }
@@ -176,9 +201,6 @@ const Wizard = () => {
176201 />
177202 </ TransitionBox >
178203 </ Box >
179- < Box sx = { { ...pageSX , textAlign : 'left' } } >
180- < WizardExtra onBack = { ( ) => setPage ( pg => pg - 1 ) } multiRace = { multiRace } onAddElection = { onAddElection } />
181- </ Box >
182204 </ Box >
183205 </ Paper >
184206 </ ElectionContextProvider >
0 commit comments