@asd20/ui-next 2.35.2 → 2.35.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.35.3](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.35.2...ui-next-v2.35.3) (2026-11-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* give transcript requests a prominent alternate path in the student records form ([1250f67](https://github.com/academydistrict20/asd20-ui-next/commit/1250f6773bfa0047a234ea064463408cf4e51713))
|
|
9
|
+
|
|
3
10
|
## [2.35.2](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.35.1...ui-next-v2.35.2) (2026-11-04)
|
|
4
11
|
|
|
5
12
|
|
package/package.json
CHANGED
|
@@ -49,8 +49,37 @@
|
|
|
49
49
|
novalidate
|
|
50
50
|
@submit.prevent="validateBeforeCaptcha"
|
|
51
51
|
>
|
|
52
|
+
<asd20-public-form-field
|
|
53
|
+
v-if="requestTypeField"
|
|
54
|
+
class="asd20-public-form-modal__requester-choice asd20-public-form-modal__records-choice"
|
|
55
|
+
:field="requestTypeField"
|
|
56
|
+
:input-id="fieldId('requesterType')"
|
|
57
|
+
:model-value="values.requesterType"
|
|
58
|
+
:errors="validationErrors.requesterType || []"
|
|
59
|
+
:show-pristine-validation-errors="false"
|
|
60
|
+
@update:modelValue="updateValue('requesterType', $event)"
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
<section v-if="isStudentRecordsForm && values.requesterType === 'transcriptOnly'">
|
|
64
|
+
<h3>Request a Transcript</h3>
|
|
65
|
+
<p>
|
|
66
|
+
For transcripts, graduation verification or replacement diplomas,
|
|
67
|
+
please use our dedicated Transcripts and Diplomas page.
|
|
68
|
+
</p>
|
|
69
|
+
<p>
|
|
70
|
+
<a :href="transcriptsHref">Go to Transcripts and Diplomas</a>
|
|
71
|
+
</p>
|
|
72
|
+
</section>
|
|
73
|
+
|
|
74
|
+
<template v-if="isStudentRecordsForm && showRequestFields">
|
|
75
|
+
<h3>{{ recordsRequestTitle }}</h3>
|
|
76
|
+
<p v-if="requestTypeDescription">
|
|
77
|
+
{{ requestTypeDescription }}
|
|
78
|
+
</p>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
52
81
|
<p
|
|
53
|
-
v-if="formDefinition.introduction"
|
|
82
|
+
v-if="showRequestFields && formDefinition.introduction"
|
|
54
83
|
class="asd20-public-form-modal__introduction"
|
|
55
84
|
>
|
|
56
85
|
<template v-if="inlineIntroductionParts">
|
|
@@ -65,7 +94,7 @@
|
|
|
65
94
|
</p>
|
|
66
95
|
<p
|
|
67
96
|
v-if="
|
|
68
|
-
formDefinition.introductionLink &&
|
|
97
|
+
showRequestFields && formDefinition.introductionLink &&
|
|
69
98
|
!formDefinition.introductionLink.inline
|
|
70
99
|
"
|
|
71
100
|
>
|
|
@@ -128,6 +157,7 @@
|
|
|
128
157
|
</div>
|
|
129
158
|
|
|
130
159
|
<div
|
|
160
|
+
v-if="showRequestFields"
|
|
131
161
|
class="asd20-public-form-modal__submit"
|
|
132
162
|
aria-live="polite"
|
|
133
163
|
>
|
|
@@ -197,6 +227,29 @@
|
|
|
197
227
|
</div>
|
|
198
228
|
</form>
|
|
199
229
|
</asd20-viewport>
|
|
230
|
+
|
|
231
|
+
<asd20-modal
|
|
232
|
+
:open="open && showTranscriptReminder"
|
|
233
|
+
title="Transcript-only request"
|
|
234
|
+
dismissable
|
|
235
|
+
@dismiss="showTranscriptReminder = false"
|
|
236
|
+
>
|
|
237
|
+
<asd20-viewport scrollable>
|
|
238
|
+
<p>
|
|
239
|
+
You selected only a transcript. Most transcript-only requests should be
|
|
240
|
+
submitted through our Transcripts and Diplomas page.
|
|
241
|
+
</p>
|
|
242
|
+
<p>
|
|
243
|
+
<a :href="transcriptsHref">Go to Transcripts and Diplomas</a>
|
|
244
|
+
</p>
|
|
245
|
+
<asd20-button
|
|
246
|
+
label="Back to request"
|
|
247
|
+
horizontal
|
|
248
|
+
bordered
|
|
249
|
+
@click="showTranscriptReminder = false"
|
|
250
|
+
/>
|
|
251
|
+
</asd20-viewport>
|
|
252
|
+
</asd20-modal>
|
|
200
253
|
</asd20-modal>
|
|
201
254
|
</template>
|
|
202
255
|
|
|
@@ -221,6 +274,11 @@ import {
|
|
|
221
274
|
submitPublicForm,
|
|
222
275
|
} from '../../../helpers/publicForms'
|
|
223
276
|
import createComponentInstanceId from '../../../utils/createComponentInstanceId'
|
|
277
|
+
import {
|
|
278
|
+
studentRecordsPresentation,
|
|
279
|
+
studentRecordsSubmissionValues,
|
|
280
|
+
usesStudentRecordsRouting,
|
|
281
|
+
} from '../../../helpers/studentRecordsRequest'
|
|
224
282
|
|
|
225
283
|
const DEFAULT_CAPTCHA_SITEKEY = '6LfidKoUAAAAAFqr3QEbia3jIkecsZyxBYlMvWrX'
|
|
226
284
|
|
|
@@ -253,6 +311,9 @@ export default {
|
|
|
253
311
|
instanceId,
|
|
254
312
|
honeypotId: `${instanceId}-website`,
|
|
255
313
|
formDefinition: null,
|
|
314
|
+
serviceDefinition: null,
|
|
315
|
+
transcriptsHref: 'https://www.asd20.org/transcripts-and-diplomas/',
|
|
316
|
+
showTranscriptReminder: false,
|
|
256
317
|
values: {},
|
|
257
318
|
honeypot: '',
|
|
258
319
|
validationErrors: {},
|
|
@@ -271,12 +332,15 @@ export default {
|
|
|
271
332
|
canSubmit() {
|
|
272
333
|
return Boolean(
|
|
273
334
|
this.formDefinition &&
|
|
335
|
+
this.showRequestFields &&
|
|
274
336
|
!this.sending &&
|
|
275
337
|
validatePublicFormValues(this.formDefinition, this.values).valid
|
|
276
338
|
)
|
|
277
339
|
},
|
|
278
340
|
visibleFields() {
|
|
279
341
|
return (this.formDefinition?.fields || [])
|
|
342
|
+
.filter(field => !this.isStudentRecordsForm ||
|
|
343
|
+
(this.showRequestFields && field.name !== 'requesterType'))
|
|
280
344
|
.filter(field => isPublicFormFieldVisible(field, this.values))
|
|
281
345
|
.map(field => ({
|
|
282
346
|
...field,
|
|
@@ -286,6 +350,27 @@ export default {
|
|
|
286
350
|
modalTitle() {
|
|
287
351
|
return this.formDefinition?.title || 'Public Form'
|
|
288
352
|
},
|
|
353
|
+
isStudentRecordsForm() {
|
|
354
|
+
return usesStudentRecordsRouting(this.formDefinition)
|
|
355
|
+
},
|
|
356
|
+
requestTypeField() {
|
|
357
|
+
return this.isStudentRecordsForm
|
|
358
|
+
? this.formDefinition.fields.find(field => field.name === 'requesterType')
|
|
359
|
+
: null
|
|
360
|
+
},
|
|
361
|
+
showRequestFields() {
|
|
362
|
+
return !this.isStudentRecordsForm ||
|
|
363
|
+
['schoolTransfer', 'studentRecords'].includes(this.values.requesterType)
|
|
364
|
+
},
|
|
365
|
+
recordsRequestTitle() {
|
|
366
|
+
return this.values.requesterType === 'schoolTransfer'
|
|
367
|
+
? 'School Transfer Records Request'
|
|
368
|
+
: 'Student Records Request (FERPA Requests Only)'
|
|
369
|
+
},
|
|
370
|
+
requestTypeDescription() {
|
|
371
|
+
return this.serviceDefinition?.fields.find(field => field.name === 'requesterType')
|
|
372
|
+
?.options.find(option => option.value === this.values.requesterType)?.description
|
|
373
|
+
},
|
|
289
374
|
inlineIntroductionParts() {
|
|
290
375
|
const introduction = this.formDefinition?.introduction || ''
|
|
291
376
|
const link = this.formDefinition?.introductionLink
|
|
@@ -371,6 +456,7 @@ export default {
|
|
|
371
456
|
this.validationErrors = {}
|
|
372
457
|
this.validationAttempted = false
|
|
373
458
|
this.honeypot = ''
|
|
459
|
+
this.showTranscriptReminder = false
|
|
374
460
|
|
|
375
461
|
try {
|
|
376
462
|
const expectedId = normalizeFormId(this.formId)
|
|
@@ -387,11 +473,13 @@ export default {
|
|
|
387
473
|
)
|
|
388
474
|
if (sequence !== this.loadSequence) return
|
|
389
475
|
|
|
390
|
-
this.
|
|
391
|
-
this.
|
|
476
|
+
this.serviceDefinition = normalized
|
|
477
|
+
this.formDefinition = studentRecordsPresentation(normalized)
|
|
478
|
+
this.values = createPublicFormValues(this.formDefinition)
|
|
392
479
|
} catch (_error) {
|
|
393
480
|
if (sequence !== this.loadSequence) return
|
|
394
481
|
this.formDefinition = null
|
|
482
|
+
this.serviceDefinition = null
|
|
395
483
|
this.definitionError =
|
|
396
484
|
'This form could not be loaded. Please try again later or use another contact option on this page.'
|
|
397
485
|
this.$nextTick(() => this.$refs.definitionError?.focus?.())
|
|
@@ -406,6 +494,15 @@ export default {
|
|
|
406
494
|
return field.showCharacterCount === true
|
|
407
495
|
},
|
|
408
496
|
updateValue(name, value) {
|
|
497
|
+
if (this.isStudentRecordsForm && name === 'requesterType' && this.values[name] !== value) {
|
|
498
|
+
this.values.transferRecordTypes = []
|
|
499
|
+
this.values.otherRecordsDescription = ''
|
|
500
|
+
this.showTranscriptReminder = false
|
|
501
|
+
this.validationErrors = {}
|
|
502
|
+
this.validationAttempted = false
|
|
503
|
+
this.feedbackMessage = ''
|
|
504
|
+
this.submissionFailed = false
|
|
505
|
+
}
|
|
409
506
|
this.values[name] = value
|
|
410
507
|
for (const field of this.formDefinition.fields) {
|
|
411
508
|
if (!isPublicFormFieldVisible(field, this.values)) {
|
|
@@ -416,7 +513,7 @@ export default {
|
|
|
416
513
|
if (this.validationAttempted) this.validate(false)
|
|
417
514
|
},
|
|
418
515
|
validate(focusSummary = true) {
|
|
419
|
-
if (!this.formDefinition) return false
|
|
516
|
+
if (!this.formDefinition || !this.showRequestFields) return false
|
|
420
517
|
const result = validatePublicFormValues(this.formDefinition, this.values)
|
|
421
518
|
this.values = result.values
|
|
422
519
|
this.validationErrors = result.errors
|
|
@@ -428,12 +525,21 @@ export default {
|
|
|
428
525
|
return result.valid
|
|
429
526
|
},
|
|
430
527
|
validateBeforeCaptcha(event) {
|
|
431
|
-
if (this.sending || !this.validate()) {
|
|
528
|
+
if (this.sending || !this.validate() || this.checkTranscriptOnly()) {
|
|
432
529
|
event?.preventDefault?.()
|
|
433
530
|
return false
|
|
434
531
|
}
|
|
435
532
|
return true
|
|
436
533
|
},
|
|
534
|
+
checkTranscriptOnly() {
|
|
535
|
+
if (this.isStudentRecordsForm &&
|
|
536
|
+
this.values.transferRecordTypes.length === 1 &&
|
|
537
|
+
this.values.transferRecordTypes[0] === 'academicRecords') {
|
|
538
|
+
this.showTranscriptReminder = true
|
|
539
|
+
return true
|
|
540
|
+
}
|
|
541
|
+
return false
|
|
542
|
+
},
|
|
437
543
|
focusField(name) {
|
|
438
544
|
document.getElementById(this.fieldId(name))?.focus?.()
|
|
439
545
|
},
|
|
@@ -480,7 +586,7 @@ export default {
|
|
|
480
586
|
this.$nextTick(() => this.$refs.feedback?.focus?.())
|
|
481
587
|
},
|
|
482
588
|
async submitForm(captchaToken = '') {
|
|
483
|
-
if (this.sending || !this.validate()) return
|
|
589
|
+
if (this.sending || !this.validate() || this.checkTranscriptOnly()) return
|
|
484
590
|
if (this.captchaEnabled && !captchaToken) {
|
|
485
591
|
this.showFeedback(
|
|
486
592
|
'Verification is required before submitting this form.'
|
|
@@ -494,8 +600,11 @@ export default {
|
|
|
494
600
|
try {
|
|
495
601
|
const payload = {
|
|
496
602
|
...(await serializePublicFormValues(
|
|
497
|
-
this.
|
|
498
|
-
|
|
603
|
+
this.serviceDefinition,
|
|
604
|
+
validatePublicFormValues(
|
|
605
|
+
this.serviceDefinition,
|
|
606
|
+
studentRecordsSubmissionValues(this.serviceDefinition, this.values)
|
|
607
|
+
).values
|
|
499
608
|
)),
|
|
500
609
|
website: this.honeypot,
|
|
501
610
|
captchaToken,
|
|
@@ -555,6 +664,12 @@ export default {
|
|
|
555
664
|
align-items: flex-start;
|
|
556
665
|
}
|
|
557
666
|
|
|
667
|
+
&__records-choice :deep(legend) {
|
|
668
|
+
font-size: 1.5rem;
|
|
669
|
+
font-weight: 700;
|
|
670
|
+
margin-bottom: 0.5rem;
|
|
671
|
+
}
|
|
672
|
+
|
|
558
673
|
&__state,
|
|
559
674
|
&__submit {
|
|
560
675
|
align-items: center;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Presentation for the unified form; the service still accepts its v8 wire fields.
|
|
2
|
+
export function usesStudentRecordsRouting(definition) {
|
|
3
|
+
return (
|
|
4
|
+
definition?.id === 'student-records-request' &&
|
|
5
|
+
definition.fields.some(field =>
|
|
6
|
+
field.name === 'requesterType' &&
|
|
7
|
+
field.options?.some(option => option.value === 'schoolTransfer')
|
|
8
|
+
) &&
|
|
9
|
+
definition.fields.some(field => field.name === 'transferRecordTypes')
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function studentRecordsPresentation(definition) {
|
|
14
|
+
if (!usesStudentRecordsRouting(definition)) return definition
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
...definition,
|
|
18
|
+
fields: definition.fields
|
|
19
|
+
.filter(field => field.name !== 'recordsRequested')
|
|
20
|
+
.map(field => {
|
|
21
|
+
if (field.name === 'requesterType') {
|
|
22
|
+
return {
|
|
23
|
+
...field,
|
|
24
|
+
label: 'What records do you need?',
|
|
25
|
+
options: [
|
|
26
|
+
{
|
|
27
|
+
label: 'Transcript or graduation verification only',
|
|
28
|
+
value: 'transcriptOnly',
|
|
29
|
+
},
|
|
30
|
+
{ label: 'School transfer records', value: 'schoolTransfer' },
|
|
31
|
+
{ label: 'Other student records', value: 'studentRecords' },
|
|
32
|
+
],
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (field.name === 'transferRecordTypes') {
|
|
36
|
+
return {
|
|
37
|
+
...field,
|
|
38
|
+
visibleWhen: {
|
|
39
|
+
fieldName: 'requesterType',
|
|
40
|
+
values: ['schoolTransfer', 'studentRecords'],
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return field
|
|
45
|
+
}),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function studentRecordsSubmissionValues(definition, values) {
|
|
50
|
+
if (
|
|
51
|
+
!usesStudentRecordsRouting(definition) ||
|
|
52
|
+
values.requesterType !== 'studentRecords'
|
|
53
|
+
) {
|
|
54
|
+
return values
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const options = definition.fields.find(
|
|
58
|
+
field => field.name === 'transferRecordTypes'
|
|
59
|
+
).options
|
|
60
|
+
const labels = values.transferRecordTypes.map(value => {
|
|
61
|
+
const label = options.find(option => option.value === value).label
|
|
62
|
+
return value === 'otherRecords'
|
|
63
|
+
? `${label}: ${values.otherRecordsDescription}`
|
|
64
|
+
: label
|
|
65
|
+
})
|
|
66
|
+
return {
|
|
67
|
+
...values,
|
|
68
|
+
recordsRequested: labels.join('\n'),
|
|
69
|
+
transferRecordTypes: [],
|
|
70
|
+
otherRecordsDescription: '',
|
|
71
|
+
}
|
|
72
|
+
}
|