Skip to content

Commit 88b5a17

Browse files
committed
fix: prevent multiple contest submissions and fix alert z-index
1 parent 019260e commit 88b5a17

File tree

1 file changed

+61
-33
lines changed

1 file changed

+61
-33
lines changed

frontend/src/components/CreateContestModal.vue

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,44 @@
66
<!-- Modal header with Wikipedia primary color -->
77
<div class="modal-header">
88
<h5 class="modal-title">Create New Contest</h5>
9-
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
9+
<button type="button" class="btn-close" data-bs-dismiss="modal" :disabled="loading"></button>
1010
</div>
1111
<div class="modal-body">
1212
<form @submit.prevent="handleSubmit">
1313
<!-- Basic contest information: name and project -->
1414
<div class="row">
1515
<div class="col-md-6 mb-3">
1616
<label for="contestName" class="form-label">Contest Name *</label>
17-
<input type="text" class="form-control" id="contestName" v-model="formData.name" required />
17+
<input type="text" class="form-control" id="contestName" v-model="formData.name" required
18+
:disabled="loading" />
1819
</div>
1920
<div class="col-md-6 mb-3">
2021
<label for="projectName" class="form-label">Project Name *</label>
21-
<input type="text" class="form-control" id="projectName" v-model="formData.project_name" required />
22+
<input type="text" class="form-control" id="projectName" v-model="formData.project_name" required
23+
:disabled="loading" />
2224
</div>
2325
</div>
2426

2527
<!-- Contest description field -->
2628
<div class="mb-3">
2729
<label for="contestDescription" class="form-label">Description</label>
28-
<textarea class="form-control" id="contestDescription" rows="3" v-model="formData.description"></textarea>
30+
<textarea class="form-control" id="contestDescription" rows="3" v-model="formData.description"
31+
:disabled="loading"></textarea>
2932
</div>
3033

3134
<!-- Contest rules - required field -->
3235
<div class="mb-3">
3336
<label for="contestRules" class="form-label">Contest Rules *</label>
3437
<textarea class="form-control" id="contestRules" rows="4"
35-
placeholder="Write rules about how articles must be submitted." v-model="formData.rules_text"
36-
required></textarea>
38+
placeholder="Write rules about how articles must be submitted." v-model="formData.rules_text" required
39+
:disabled="loading"></textarea>
3740
</div>
3841

3942
<!-- Submission type selector: new, expansion, or both -->
4043
<div class="mb-3">
4144
<label for="allowedType" class="form-label">Allowed Submission Type</label>
42-
<select id="allowedType" class="form-control" v-model="formData.allowed_submission_type">
45+
<select id="allowedType" class="form-control" v-model="formData.allowed_submission_type"
46+
:disabled="loading">
4347
<option value="new">New Article Only</option>
4448
<option value="expansion">Improved Article Only</option>
4549
<option value="both">Both(New Article + Improved Article)</option>
@@ -50,11 +54,13 @@
5054
<div class="row">
5155
<div class="col-md-6 mb-3">
5256
<label for="startDate" class="form-label">Start Date *</label>
53-
<input type="date" class="form-control" id="startDate" v-model="formData.start_date" required />
57+
<input type="date" class="form-control" id="startDate" v-model="formData.start_date" required
58+
:disabled="loading" />
5459
</div>
5560
<div class="col-md-6 mb-3">
5661
<label for="endDate" class="form-label">End Date *</label>
57-
<input type="date" class="form-control" id="endDate" v-model="formData.end_date" required />
62+
<input type="date" class="form-control" id="endDate" v-model="formData.end_date" required
63+
:disabled="loading" />
5864
</div>
5965
</div>
6066

@@ -75,16 +81,18 @@
7581
<!-- Simple Scoring Option -->
7682
<div class="col-md-6">
7783
<label :class="{ 'active': !enableMultiParameterScoring }">
78-
<input type="radio" name="scoringMode" :value="false" v-model="enableMultiParameterScoring" />
79-
<h6>Simple Scoring</h6>
84+
<input type="radio" name="scoringMode" :value="false" v-model="enableMultiParameterScoring"
85+
:disabled="loading" />
86+
<h6>Simple Scoring</h6>
8087
</label>
8188
</div>
8289

8390
<!-- Multi-Parameter Scoring Option -->
8491
<div class="col-md-6">
8592
<label :class="{ 'active': enableMultiParameterScoring }">
86-
<input type="radio" name="scoringMode" :value="true" v-model="enableMultiParameterScoring" />
87-
<div >
93+
<input type="radio" name="scoringMode" :value="true" v-model="enableMultiParameterScoring"
94+
:disabled="loading" />
95+
<div>
8896
<h6>Multi-Parameter Scoring</h6>
8997
</div>
9098
</label>
@@ -102,7 +110,7 @@
102110
<div class="col-md-6 mb-3">
103111
<label for="marksAccepted" class="form-label">Points for Accepted Submissions *</label>
104112
<input type="number" class="form-control" id="marksAccepted"
105-
v-model.number="formData.marks_setting_accepted" min="0" required />
113+
v-model.number="formData.marks_setting_accepted" min="0" required :disabled="loading" />
106114
<small class="form-text text-muted">
107115
Maximum points that can be awarded. Jury can assign points from 0 up to this value for accepted
108116
submissions.
@@ -111,7 +119,7 @@
111119
<div class="col-md-6 mb-3">
112120
<label for="marksRejected" class="form-label">Points for Rejected Submissions *</label>
113121
<input type="number" class="form-control" id="marksRejected"
114-
v-model.number="formData.marks_setting_rejected" min="0" required />
122+
v-model.number="formData.marks_setting_rejected" min="0" required :disabled="loading" />
115123
<small class="form-text text-muted">
116124
Fixed points awarded automatically for rejected submissions (usually 0 or negative).
117125
</small>
@@ -132,14 +140,14 @@
132140
<div class="col-md-6">
133141
<label class="form-label">Maximum Score (Accepted Submissions) *</label>
134142
<input type="number" class="form-control" v-model.number="maxScore" min="1" max="100"
135-
placeholder="0" required />
143+
placeholder="0" required :disabled="loading" />
136144
<small class="text-muted">Final calculated score will be scaled to this value</small>
137145
</div>
138146

139147
<div class="col-md-6">
140148
<label class="form-label">Minimum Score (Rejected Submissions) *</label>
141149
<input type="number" class="form-control" v-model.number="minScore" min="0" max="100"
142-
placeholder="0" required />
150+
placeholder="0" required :disabled="loading" />
143151
<small class="text-muted">Score for rejected submissions</small>
144152
</div>
145153
</div>
@@ -158,22 +166,22 @@
158166
<div class="row align-items-center">
159167
<div class="col-md-3">
160168
<input type="text" class="form-control" v-model="param.name" placeholder="Parameter name"
161-
required />
169+
required :disabled="loading" />
162170
</div>
163171
<div class="col-md-3">
164172
<div class="input-group">
165173
<input type="number" class="form-control" v-model.number="param.weight" min="0" max="100"
166-
placeholder="Weight" required />
174+
placeholder="Weight" required :disabled="loading" />
167175
<span class="input-group-text">%</span>
168176
</div>
169177
</div>
170178
<div class="col-md-5">
171179
<input type="text" class="form-control" v-model="param.description"
172-
placeholder="Description (optional)" />
180+
placeholder="Description (optional)" :disabled="loading" />
173181
</div>
174182
<div class="col-md-1 text-end">
175183
<button type="button" class="btn btn-sm btn-outline-danger" @click="removeParameter(index)"
176-
:disabled="scoringParameters.length <= 1">
184+
:disabled="scoringParameters.length <= 1 || loading">
177185
<i class="fas fa-times"></i>
178186
</button>
179187
</div>
@@ -182,7 +190,8 @@
182190
</div>
183191
</div>
184192

185-
<button type="button" class="btn btn-sm btn-outline-primary mt-2" @click="addParameter">
193+
<button type="button" class="btn btn-sm btn-outline-primary mt-2" @click="addParameter"
194+
:disabled="loading">
186195
<i class="fas fa-plus me-1"></i>Add Parameter
187196
</button>
188197

@@ -201,7 +210,8 @@
201210
</div>
202211

203212
<!-- Reset to default parameters -->
204-
<button type="button" class="btn btn-sm btn-outline-secondary" @click="loadDefaultParameters">
213+
<button type="button" class="btn btn-sm btn-outline-secondary" @click="loadDefaultParameters"
214+
:disabled="loading">
205215
<i class="fas fa-redo me-1"></i>Load Default Parameters
206216
</button>
207217
</div>
@@ -227,7 +237,7 @@
227237
<!-- Organizer search input with autocomplete dropdown -->
228238
<div style="position: relative;">
229239
<input type="text" class="form-control" v-model="organizerSearchQuery" @input="searchOrganizers"
230-
placeholder="Type username to add additional organizers..." autocomplete="off" />
240+
placeholder="Type username to add additional organizers..." autocomplete="off" :disabled="loading" />
231241

232242
<!-- Autocomplete results dropdown -->
233243
<div v-if="organizerSearchResults.length > 0 && organizerSearchQuery.length >= 2"
@@ -277,7 +287,7 @@
277287
<!-- Jury search input with autocomplete dropdown -->
278288
<div style="position: relative;">
279289
<input type="text" class="form-control" id="juryInput" v-model="jurySearchQuery" @input="searchJury"
280-
placeholder="Type username to search..." autocomplete="off" />
290+
placeholder="Type username to search..." autocomplete="off" :disabled="loading" />
281291
<!-- Autocomplete results with self-selection warning -->
282292
<div v-if="jurySearchResults.length > 0 && jurySearchQuery.length >= 2"
283293
class="jury-autocomplete position-absolute w-100 border rounded-bottom"
@@ -304,15 +314,15 @@
304314
<div class="mb-3">
305315
<label for="minByteCount" class="form-label">Minimum Byte Count *</label>
306316
<input type="number" class="form-control" id="minByteCount" v-model.number="formData.min_byte_count"
307-
min="0" placeholder="e.g., 1000" required />
317+
min="0" placeholder="e.g., 1000" required :disabled="loading" />
308318
<small class="form-text text-muted">Articles must have at least this many bytes</small>
309319
</div>
310320

311321
<!-- Minimum reference/citation requirement -->
312322
<div class="mb-3">
313323
<label for="minReferenceCount" class="form-label">Minimum Reference Count</label>
314324
<input type="number" class="form-control" id="minReferenceCount"
315-
v-model.number="formData.min_reference_count" min="0" placeholder="e.g., 5" />
325+
v-model.number="formData.min_reference_count" min="0" placeholder="e.g., 5" :disabled="loading" />
316326
<small class="form-text text-muted">
317327
Articles must have at least this many references (external links). Leave as 0 for no requirement.
318328
</small>
@@ -329,15 +339,15 @@
329339
<div class="input-group">
330340
<input type="url" class="form-control" v-model="formData.categories[index]"
331341
:placeholder="index === 0 ? 'https://en.wikipedia.org/wiki/Category:Example' : 'Add another category URL'"
332-
required />
342+
required :disabled="loading" />
333343
<button v-if="formData.categories.length > 1" type="button" class="btn btn-outline-danger"
334-
@click="removeCategory(index)" title="Remove category">
344+
@click="removeCategory(index)" title="Remove category" :disabled="loading">
335345
<i class="fas fa-times"></i>
336346
</button>
337347
</div>
338348
</div>
339349

340-
<button type="button" class="btn btn-outline-primary btn-sm" @click="addCategory">
350+
<button type="button" class="btn btn-outline-primary btn-sm" @click="addCategory" :disabled="loading">
341351
<i class="fas fa-plus me-1"></i>Add Category
342352
</button>
343353

@@ -353,7 +363,7 @@
353363
<span class="badge bg-secondary ms-1">Optional</span>
354364
</label>
355365
<input type="url" class="form-control" id="templateLink" v-model="formData.template_link"
356-
placeholder="https://en.wikipedia.org/wiki/Template:YourContestTemplate" />
366+
placeholder="https://en.wikipedia.org/wiki/Template:YourContestTemplate" :disabled="loading" />
357367
<small class="form-text text-muted d-block mt-2">
358368
<i class="fas fa-info-circle me-1"></i>
359369
If set, this template will be automatically added to submitted articles that don't already have it.
@@ -366,8 +376,14 @@
366376

367377
<!-- Modal footer with action buttons -->
368378
<div class="modal-footer">
369-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
370-
<button type="submit" class="btn btn-primary" @click="handleSubmit">Create Contest</button>
379+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" :disabled="loading">Cancel</button>
380+
<button type="submit" class="btn btn-primary" @click="handleSubmit" :disabled="loading">
381+
<span v-if="loading">
382+
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
383+
Creating...
384+
</span>
385+
<span v-else>Create Contest</span>
386+
</button>
371387
</div>
372388
</div>
373389
</div>
@@ -694,6 +710,11 @@ export default {
694710
695711
// Validate and submit contest creation form
696712
const handleSubmit = async () => {
713+
// Prevent multiple submissions
714+
if (loading.value) {
715+
return
716+
}
717+
697718
// Basic validation
698719
if (!formData.name.trim()) {
699720
showAlert('Contest name is required', 'warning')
@@ -792,7 +813,9 @@ export default {
792813
}
793814
}
794815
816+
// Set loading state to prevent multiple submissions
795817
loading.value = true
818+
796819
try {
797820
// Build scoring parameters payload
798821
let scoringParametersPayload = null
@@ -855,13 +878,17 @@ export default {
855878
if (result.success) {
856879
showAlert('Contest created successfully!', 'success')
857880
emit('created')
881+
882+
// Wait a bit for alert to show
858883
await new Promise(resolve => setTimeout(resolve, 100))
884+
859885
// Close modal programmatically
860886
const modalElement = document.getElementById('createContestModal')
861887
const modal = bootstrap.Modal.getInstance(modalElement)
862888
if (modal) {
863889
modal.hide()
864890
}
891+
865892
// Reset form
866893
resetForm()
867894
} else {
@@ -870,6 +897,7 @@ export default {
870897
} catch (error) {
871898
showAlert('Failed to create contest: ' + error.message, 'danger')
872899
} finally {
900+
// Always reset loading state
873901
loading.value = false
874902
}
875903
}

0 commit comments

Comments
 (0)