@@ -29,7 +29,10 @@ const createPollSchema = z.object({
29
29
title : z . string ( ) . min ( 1 , "Poll title is required" ) ,
30
30
mode : z . enum ( [ "normal" , "point" , "rank" ] ) ,
31
31
visibility : z . enum ( [ "public" , "private" ] ) ,
32
- groupId : z . string ( ) . min ( 1 , "Please select a group" ) ,
32
+ groupId : z . string ( ) . min ( 1 , "Please select a group" ) . refine ( ( val ) => {
33
+ // This will be validated in the onSubmit function with the actual groups data
34
+ return true ;
35
+ } , "Please select a valid group" ) ,
33
36
options : z
34
37
. array ( z . string ( ) . min ( 1 , "Option cannot be empty" ) )
35
38
. min ( 2 , "At least 2 options required" ) ,
@@ -70,13 +73,28 @@ export default function CreatePoll() {
70
73
} ,
71
74
} ) ;
72
75
76
+ // Helper function to sort groups: chartered first, then by name
77
+ const sortGroupsByCharterStatus = ( groups : Group [ ] ) => {
78
+ return [ ...groups ] . sort ( ( a , b ) => {
79
+ const aChartered = a . charter && a . charter . trim ( ) !== "" ;
80
+ const bChartered = b . charter && b . charter . trim ( ) !== "" ;
81
+
82
+ if ( aChartered && ! bChartered ) return - 1 ;
83
+ if ( ! aChartered && bChartered ) return 1 ;
84
+
85
+ // If both have same charter status, sort by name
86
+ return a . name . localeCompare ( b . name ) ;
87
+ } ) ;
88
+ } ;
89
+
73
90
// Fetch user's groups on component mount
74
91
useEffect ( ( ) => {
75
92
const fetchGroups = async ( ) => {
76
93
try {
77
94
const userGroups = await pollApi . getUserGroups ( ) ;
78
- // Ensure groups is always an array
79
- setGroups ( Array . isArray ( userGroups ) ? userGroups : [ ] ) ;
95
+ // Ensure groups is always an array and sort by charter status
96
+ const sortedGroups = Array . isArray ( userGroups ) ? sortGroupsByCharterStatus ( userGroups ) : [ ] ;
97
+ setGroups ( sortedGroups ) ;
80
98
} catch ( error ) {
81
99
console . error ( "Failed to fetch groups:" , error ) ;
82
100
setGroups ( [ ] ) ; // Set empty array on error
@@ -128,6 +146,26 @@ export default function CreatePoll() {
128
146
const onSubmit = async ( data : CreatePollForm ) => {
129
147
setIsSubmitting ( true ) ;
130
148
try {
149
+ // Validate that the selected group is chartered
150
+ const selectedGroup = groups . find ( group => group . id === data . groupId ) ;
151
+ if ( ! selectedGroup ) {
152
+ toast ( {
153
+ title : "Error" ,
154
+ description : "Please select a valid group" ,
155
+ variant : "destructive" ,
156
+ } ) ;
157
+ return ;
158
+ }
159
+
160
+ if ( ! selectedGroup . charter || selectedGroup . charter . trim ( ) === "" ) {
161
+ toast ( {
162
+ title : "Error" ,
163
+ description : "Only chartered groups can create polls. Please select a group with a charter." ,
164
+ variant : "destructive" ,
165
+ } ) ;
166
+ return ;
167
+ }
168
+
131
169
// Convert local deadline to UTC before sending to backend
132
170
let utcDeadline : string | undefined ;
133
171
if ( data . deadline ) {
@@ -152,11 +190,18 @@ export default function CreatePoll() {
152
190
} ) ;
153
191
154
192
router . push ( "/" ) ;
155
- } catch ( error ) {
193
+ } catch ( error : any ) {
156
194
console . error ( "Failed to create poll:" , error ) ;
195
+
196
+ // Show specific error message from backend if available
197
+ let errorMessage = "Failed to create poll. Please try again." ;
198
+ if ( error ?. response ?. data ?. error ) {
199
+ errorMessage = error . response . data . error ;
200
+ }
201
+
157
202
toast ( {
158
203
title : "Error" ,
159
- description : "Failed to create poll. Please try again." ,
204
+ description : errorMessage ,
160
205
variant : "destructive" ,
161
206
} ) ;
162
207
} finally {
@@ -197,6 +242,15 @@ export default function CreatePoll() {
197
242
< Label className = "text-sm font-semibold text-gray-700" >
198
243
Group
199
244
</ Label >
245
+ { ! isLoadingGroups && groups . length > 0 && (
246
+ < div className = "mt-1 mb-2 text-xs text-gray-600" >
247
+ { ( ( ) => {
248
+ const charteredCount = groups . filter ( group => group . charter && group . charter . trim ( ) !== "" ) . length ;
249
+ const totalCount = groups . length ;
250
+ return `${ charteredCount } of ${ totalCount } groups are chartered` ;
251
+ } ) ( ) }
252
+ </ div >
253
+ ) }
200
254
< Select onValueChange = { ( value ) => setValue ( "groupId" , value ) } >
201
255
< SelectTrigger className = "w-full mt-2" >
202
256
< SelectValue placeholder = "Select a group" />
@@ -207,14 +261,66 @@ export default function CreatePoll() {
207
261
) : ! Array . isArray ( groups ) || groups . length === 0 ? (
208
262
< SelectItem value = "no-groups" disabled > No groups found. Create one!</ SelectItem >
209
263
) : (
210
- groups . map ( ( group ) => (
211
- < SelectItem key = { group . id } value = { group . id } >
212
- { group . name }
213
- </ SelectItem >
214
- ) )
264
+ ( ( ) => {
265
+ const charteredGroups = groups . filter ( group => group . charter && group . charter . trim ( ) !== "" ) ;
266
+ const nonCharteredGroups = groups . filter ( group => ! group . charter || group . charter . trim ( ) === "" ) ;
267
+
268
+ return (
269
+ < >
270
+ { /* Chartered Groups */ }
271
+ { charteredGroups . length > 0 && (
272
+ < >
273
+ < div className = "px-2 py-1.5 text-xs font-semibold text-gray-500 bg-gray-50" >
274
+ Chartered Groups
275
+ </ div >
276
+ { charteredGroups . map ( ( group ) => (
277
+ < SelectItem
278
+ key = { group . id }
279
+ value = { group . id }
280
+ >
281
+ < div className = "flex items-center justify-between w-full" >
282
+ < span > { group . name } </ span >
283
+ < span className = "text-xs text-green-600 ml-2" >
284
+ ✓ Chartered
285
+ </ span >
286
+ </ div >
287
+ </ SelectItem >
288
+ ) ) }
289
+ </ >
290
+ ) }
291
+
292
+ { /* Non-Chartered Groups */ }
293
+ { nonCharteredGroups . length > 0 && (
294
+ < >
295
+ < div className = "px-2 py-1.5 text-xs font-semibold text-gray-500 bg-gray-50" >
296
+ Non-Chartered Groups
297
+ </ div >
298
+ { nonCharteredGroups . map ( ( group ) => (
299
+ < SelectItem
300
+ key = { group . id }
301
+ value = { group . id }
302
+ disabled
303
+ className = "opacity-60 cursor-not-allowed"
304
+ >
305
+ < div className = "flex items-center justify-between w-full" >
306
+ < span > { group . name } </ span >
307
+ < span className = "text-xs text-gray-500 ml-2" >
308
+ (Not chartered)
309
+ </ span >
310
+ </ div >
311
+ </ SelectItem >
312
+ ) ) }
313
+ </ >
314
+ ) }
315
+ </ >
316
+ ) ;
317
+ } ) ( )
215
318
) }
216
319
</ SelectContent >
217
320
</ Select >
321
+ < p className = "mt-1 text-sm text-gray-500" >
322
+ Only chartered groups can create polls. Groups without a charter will be disabled.
323
+ </ p >
218
324
{ errors . groupId && (
219
325
< p className = "mt-1 text-sm text-red-600" >
220
326
{ errors . groupId . message }
0 commit comments