@@ -15,6 +15,7 @@ import { NEXT_MINIMUM_START_DELAY_IN_SECONDS, PUBLIC_CHAIN, PUBLIC_MACI_VOTING_P
15
15
import { ActionCard } from "@/components/actions/action" ;
16
16
import { useMutation } from "@tanstack/react-query" ;
17
17
import classNames from "classnames" ;
18
+ import Link from "next/link" ;
18
19
19
20
enum ActionType {
20
21
Signaling ,
@@ -38,6 +39,15 @@ export default function Create() {
38
39
const { writeContract : createProposalWrite , data : createTxHash , status, error } = useWriteContract ( ) ;
39
40
const { isLoading : isConfirming , isSuccess : isConfirmed } = useWaitForTransactionReceipt ( { hash : createTxHash } ) ;
40
41
const [ actionType , setActionType ] = useState < ActionType > ( ActionType . Signaling ) ;
42
+ const [ errors , setErrors ] = useState < {
43
+ title ?: string ;
44
+ summary ?: string ;
45
+ description ?: string ;
46
+ startDate ?: string ;
47
+ endDate ?: string ;
48
+ withdrawal ?: string ;
49
+ custom ?: string ;
50
+ } > ( { } ) ;
41
51
42
52
const changeActionType = ( actionType : ActionType ) => {
43
53
setActions ( [ ] ) ;
@@ -82,20 +92,21 @@ export default function Create() {
82
92
} , [ status , createTxHash , isConfirming , isConfirmed , error , push ] ) ;
83
93
84
94
const submitProposal = async ( ) => {
95
+ let formErrors = { } ;
85
96
try {
86
97
// Check metadata
87
98
if ( ! title . trim ( ) ) {
88
- return addAlert ( "Invalid proposal details" , {
89
- description : "Please, enter a title" ,
90
- type : "error " ,
91
- } ) ;
99
+ formErrors = {
100
+ ... formErrors ,
101
+ title : "Please, enter a title " ,
102
+ } ;
92
103
}
93
104
94
105
if ( ! summary . trim ( ) ) {
95
- return addAlert ( "Invalid proposal details" , {
96
- description : "Please, enter a summary of what the proposal is about" ,
97
- type : "error " ,
98
- } ) ;
106
+ formErrors = {
107
+ ... formErrors ,
108
+ summary : "Please, enter a summary of what the proposal is about " ,
109
+ } ;
99
110
}
100
111
101
112
// Check the action
@@ -104,18 +115,18 @@ export default function Create() {
104
115
break ;
105
116
case ActionType . Withdrawal :
106
117
if ( ! actions . length ) {
107
- return addAlert ( "Invalid proposal details" , {
108
- description : "Please ensure that the withdrawal address and the amount to transfer are valid" ,
109
- type : "error " ,
110
- } ) ;
118
+ formErrors = {
119
+ ... formErrors ,
120
+ withdrawal : "Please ensure that the withdrawal address and the amount to transfer are valid " ,
121
+ } ;
111
122
}
112
123
break ;
113
124
default :
114
125
if ( ! actions . length || ! actions [ 0 ] . data || actions [ 0 ] . data === "0x" ) {
115
- return addAlert ( "Invalid proposal details" , {
116
- description : "Please ensure that the values of the action to execute are complete and correct" ,
117
- type : "error " ,
118
- } ) ;
126
+ formErrors = {
127
+ ... formErrors ,
128
+ custom : "Please ensure that the values of the action to execute are complete and correct " ,
129
+ } ;
119
130
}
120
131
}
121
132
@@ -132,21 +143,20 @@ export default function Create() {
132
143
const ipfsPin = await uploadToPinata ( blob ) ;
133
144
134
145
if ( ! startDate || ! endDate ) {
135
- addAlert ( "You need to specify the start date and end date of the voting period" , {
136
- timeout : 4 * 1000 ,
137
- } ) ;
138
- return null ;
146
+ formErrors = {
147
+ ... formErrors ,
148
+ startDate : "You need to specify the start date and end date of the voting period" ,
149
+ } ;
139
150
}
140
151
141
152
const currentTime = Math . floor ( Date . now ( ) / 1000 ) ;
142
153
const startDateTime = Math . floor ( new Date ( `${ startDate } T${ startTime ? startTime : "00:00:00" } ` ) . getTime ( ) / 1000 ) ;
143
154
144
155
if ( startDateTime - currentTime < NEXT_MINIMUM_START_DELAY_IN_SECONDS ) {
145
- addAlert ( `The start date must be at least ${ NEXT_MINIMUM_START_DELAY_IN_SECONDS } seconds in the future` , {
146
- timeout : 4 * 1000 ,
147
- type : "error" ,
148
- } ) ;
149
- return null ;
156
+ formErrors = {
157
+ ...formErrors ,
158
+ startDate : `The start date must be at least ${ NEXT_MINIMUM_START_DELAY_IN_SECONDS } seconds in the future` ,
159
+ } ;
150
160
}
151
161
152
162
const endDateTime = Math . floor ( new Date ( `${ endDate } T${ endTime ? endTime : "00:00:00" } ` ) . getTime ( ) / 1000 ) ;
@@ -160,6 +170,11 @@ export default function Create() {
160
170
tryEarlyExecution ,
161
171
] ) ;
162
172
173
+ if ( Object . keys ( formErrors ) . length > 0 ) {
174
+ setErrors ( formErrors ) ;
175
+ return ;
176
+ }
177
+
163
178
if ( chainId !== PUBLIC_CHAIN . id ) await switchChainAsync ( { chainId : PUBLIC_CHAIN . id } ) ;
164
179
createProposalWrite ( {
165
180
chainId : PUBLIC_CHAIN . id ,
@@ -199,11 +214,20 @@ export default function Create() {
199
214
const isDisabled = submitProposalMutation . isPending || isConfirming ;
200
215
return (
201
216
< section className = "container flex w-screen flex-col items-center pt-4 lg:pt-10" >
217
+ < Link className = "mb-6 mr-auto flex cursor-pointer items-center gap-2" href = "/plugins/maci-voting" >
218
+ < svg width = "20" height = "21" viewBox = "0 0 20 21" fill = "currentColor" xmlns = "http://www.w3.org/2000/svg" >
219
+ < path
220
+ d = "M6.52268 9.78578L10.9927 5.31578L9.81435 4.13745L3.33268 10.6191L9.81435 17.1008L10.9927 15.9225L6.52268 11.4525H16.666V9.78578H6.52268Z"
221
+ fill = "currentColor"
222
+ />
223
+ </ svg >
224
+ < span className = "text-base" > Back</ span >
225
+ </ Link >
202
226
< div className = "mb-6 w-full content-center justify-between" >
203
227
< h1 className = "mb-10 text-3xl font-semibold text-neutral-900" > Create Proposal</ h1 >
204
228
< div className = "mb-6" >
205
229
< InputText
206
- label = "Title"
230
+ label = "Title * "
207
231
wrapperClassName = { inputWrapperClassName }
208
232
maxLength = { 100 }
209
233
placeholder = "A short title that describes the main purpose"
@@ -216,7 +240,7 @@ export default function Create() {
216
240
< div className = "mb-6" >
217
241
< InputText
218
242
wrapperClassName = { inputWrapperClassName }
219
- label = "Summary"
243
+ label = "Summary * "
220
244
maxLength = { 240 }
221
245
placeholder = "A short summary that describes the main purpose"
222
246
variant = "default"
@@ -227,7 +251,7 @@ export default function Create() {
227
251
</ div >
228
252
< div className = "mb-6" >
229
253
< TextAreaRichText
230
- label = "Description"
254
+ label = "Description * "
231
255
wrapperClassName = { inputWrapperClassName }
232
256
className = "pt-2"
233
257
value = { description }
@@ -241,7 +265,7 @@ export default function Create() {
241
265
< InputDate
242
266
wrapperClassName = { inputWrapperClassName }
243
267
className = "w-full"
244
- label = "Start date"
268
+ label = "Start date * "
245
269
variant = "default"
246
270
value = { startDate }
247
271
onChange = { ( e ) => setStartDate ( e . target . value ) }
@@ -260,7 +284,7 @@ export default function Create() {
260
284
< InputDate
261
285
wrapperClassName = { inputWrapperClassName }
262
286
className = "w-full"
263
- label = "End date"
287
+ label = "End date * "
264
288
variant = "default"
265
289
value = { endDate }
266
290
onChange = { ( e ) => setEndDate ( e . target . value ) }
@@ -341,6 +365,14 @@ export default function Create() {
341
365
</ div >
342
366
</ div >
343
367
368
+ < div className = "flex flex-col gap-2" >
369
+ { Object . entries ( errors ) . map ( ( [ key , value ] ) => (
370
+ < div key = { key } className = "text-danger" >
371
+ { value }
372
+ </ div >
373
+ ) ) }
374
+ </ div >
375
+
344
376
< If condition = { showLoading } >
345
377
< Then >
346
378
< div className = "mb-6 mt-14" >
0 commit comments