@asd20/ui-next 2.23.4 → 2.23.5
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.23.5](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.23.4...ui-next-v2.23.5) (2026-09-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* explain directory email validation errors ([#1](https://github.com/academydistrict20/asd20-ui-next/issues/1)) ([070c408](https://github.com/academydistrict20/asd20-ui-next/commit/070c40854c1ec4f8790abfc4077da9c0aa7b44e6))
|
|
9
|
+
|
|
3
10
|
## [2.23.4](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.23.3...ui-next-v2.23.4) (2026-09-10)
|
|
4
11
|
|
|
5
12
|
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
<asd20-text-input
|
|
13
13
|
id="senderName"
|
|
14
14
|
v-model="emailMessage.senderName"
|
|
15
|
+
:validator="validateSenderName"
|
|
15
16
|
label="Your Full Name"
|
|
16
17
|
required
|
|
17
18
|
@validated="validationErrors.senderName = $event"
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
<asd20-text-input
|
|
29
30
|
id="subject"
|
|
30
31
|
v-model="emailMessage.subject"
|
|
32
|
+
:validator="validateSubject"
|
|
31
33
|
label="Subject"
|
|
32
34
|
maxlength="200"
|
|
33
35
|
required
|
|
@@ -36,8 +38,10 @@
|
|
|
36
38
|
<asd20-text-area-input
|
|
37
39
|
id="messageBody"
|
|
38
40
|
v-model="emailMessage.messageBody"
|
|
41
|
+
:validator="validateMessageBody"
|
|
39
42
|
label="Message"
|
|
40
43
|
maxlength="8000"
|
|
44
|
+
help-text="Please enter at least 5 characters."
|
|
41
45
|
required
|
|
42
46
|
@validated="validationErrors.messageBody = $event"
|
|
43
47
|
/>
|
|
@@ -46,18 +50,18 @@
|
|
|
46
50
|
aria-live="polite"
|
|
47
51
|
>
|
|
48
52
|
<Recaptcha
|
|
49
|
-
v-if="!sending && !sendSucceeded
|
|
53
|
+
v-if="!sending && !sendSucceeded"
|
|
50
54
|
sitekey="6LfidKoUAAAAAFqr3QEbia3jIkecsZyxBYlMvWrX"
|
|
51
55
|
:load-recaptcha-script="true"
|
|
52
56
|
size="invisible"
|
|
53
57
|
@verify="captchaVerified"
|
|
54
58
|
>
|
|
55
59
|
<asd20-button
|
|
56
|
-
:disabled="!isValid"
|
|
57
60
|
label="Send"
|
|
58
61
|
horizontal
|
|
59
62
|
centered
|
|
60
63
|
bordered
|
|
64
|
+
@click="validateBeforeSend"
|
|
61
65
|
/>
|
|
62
66
|
</Recaptcha>
|
|
63
67
|
<asd20-spinner
|
|
@@ -73,6 +77,40 @@
|
|
|
73
77
|
</p>
|
|
74
78
|
</div>
|
|
75
79
|
</Asd20Viewport>
|
|
80
|
+
|
|
81
|
+
<asd20-modal
|
|
82
|
+
:open="feedbackModalOpen"
|
|
83
|
+
:title="feedbackTitle"
|
|
84
|
+
icon="alert"
|
|
85
|
+
dismissable
|
|
86
|
+
windowed
|
|
87
|
+
class="asd20-compose-email-modal__feedback-modal"
|
|
88
|
+
@dismiss="dismissFeedbackModal"
|
|
89
|
+
>
|
|
90
|
+
<div
|
|
91
|
+
class="asd20-compose-email-modal__feedback"
|
|
92
|
+
role="alert"
|
|
93
|
+
>
|
|
94
|
+
<p>{{ feedbackIntroduction }}</p>
|
|
95
|
+
<ul v-if="feedbackMessages.length">
|
|
96
|
+
<li
|
|
97
|
+
v-for="message in feedbackMessages"
|
|
98
|
+
:key="message"
|
|
99
|
+
>
|
|
100
|
+
{{ message }}
|
|
101
|
+
</li>
|
|
102
|
+
</ul>
|
|
103
|
+
</div>
|
|
104
|
+
<template #footer>
|
|
105
|
+
<asd20-button
|
|
106
|
+
label="Return to form"
|
|
107
|
+
horizontal
|
|
108
|
+
centered
|
|
109
|
+
bordered
|
|
110
|
+
@click="dismissFeedbackModal"
|
|
111
|
+
/>
|
|
112
|
+
</template>
|
|
113
|
+
</asd20-modal>
|
|
76
114
|
</asd20-modal>
|
|
77
115
|
</template>
|
|
78
116
|
|
|
@@ -111,7 +149,10 @@ export default {
|
|
|
111
149
|
composing: false,
|
|
112
150
|
sending: false,
|
|
113
151
|
sendSucceeded: false,
|
|
114
|
-
|
|
152
|
+
feedbackModalOpen: false,
|
|
153
|
+
feedbackTitle: '',
|
|
154
|
+
feedbackIntroduction: '',
|
|
155
|
+
feedbackMessages: [],
|
|
115
156
|
emailMessage: {
|
|
116
157
|
senderName: '',
|
|
117
158
|
senderEmail: '',
|
|
@@ -124,16 +165,11 @@ export default {
|
|
|
124
165
|
return 'Send an Email'
|
|
125
166
|
},
|
|
126
167
|
sendStatusMessage() {
|
|
127
|
-
return
|
|
128
|
-
? "Your message couldn't be sent via this form."
|
|
129
|
-
: 'Your message was sent.'
|
|
168
|
+
return 'Your message was sent.'
|
|
130
169
|
},
|
|
131
170
|
isValid() {
|
|
132
171
|
return (
|
|
133
|
-
|
|
134
|
-
Boolean(this.emailMessage.senderEmail) &&
|
|
135
|
-
Boolean(this.emailMessage.subject) &&
|
|
136
|
-
Boolean(this.emailMessage.messageBody) &&
|
|
172
|
+
this.getFormValidationMessages().length === 0 &&
|
|
137
173
|
this.validationErrors.senderName.length === 0 &&
|
|
138
174
|
this.validationErrors.senderEmail.length === 0 &&
|
|
139
175
|
this.validationErrors.subject.length === 0 &&
|
|
@@ -176,10 +212,57 @@ export default {
|
|
|
176
212
|
const pattern =
|
|
177
213
|
/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
|
|
178
214
|
const regex = RegExp(pattern)
|
|
179
|
-
if (!regex.test(value))
|
|
215
|
+
if (!regex.test(String(value || '').trim()))
|
|
180
216
|
validationErrors.push('A valid email address is required')
|
|
181
217
|
return validationErrors
|
|
182
218
|
},
|
|
219
|
+
validateSenderName({ value, validationErrors }) {
|
|
220
|
+
if (!String(value || '').trim())
|
|
221
|
+
validationErrors.push('Your full name is required.')
|
|
222
|
+
return validationErrors
|
|
223
|
+
},
|
|
224
|
+
validateSubject({ value, validationErrors }) {
|
|
225
|
+
if (!String(value || '').trim())
|
|
226
|
+
validationErrors.push('A subject is required.')
|
|
227
|
+
return validationErrors
|
|
228
|
+
},
|
|
229
|
+
validateMessageBody({ value, validationErrors }) {
|
|
230
|
+
const message = String(value || '').trim()
|
|
231
|
+
if (message && message.length < 5)
|
|
232
|
+
validationErrors.push('The message must be at least 5 characters.')
|
|
233
|
+
return validationErrors
|
|
234
|
+
},
|
|
235
|
+
getFormValidationMessages() {
|
|
236
|
+
const messages = []
|
|
237
|
+
const senderName = String(this.emailMessage.senderName || '').trim()
|
|
238
|
+
const senderEmail = String(this.emailMessage.senderEmail || '').trim()
|
|
239
|
+
const subject = String(this.emailMessage.subject || '').trim()
|
|
240
|
+
const messageBody = String(this.emailMessage.messageBody || '').trim()
|
|
241
|
+
const emailPattern =
|
|
242
|
+
/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
|
|
243
|
+
|
|
244
|
+
if (!senderName) messages.push('Your full name is required.')
|
|
245
|
+
if (!senderEmail) messages.push('Your email address is required.')
|
|
246
|
+
else if (!emailPattern.test(senderEmail))
|
|
247
|
+
messages.push('Enter a valid email address.')
|
|
248
|
+
if (!subject) messages.push('A subject is required.')
|
|
249
|
+
if (!messageBody) messages.push('A message is required.')
|
|
250
|
+
else if (messageBody.length < 5)
|
|
251
|
+
messages.push('The message must be at least 5 characters.')
|
|
252
|
+
|
|
253
|
+
return messages
|
|
254
|
+
},
|
|
255
|
+
validateBeforeSend(event) {
|
|
256
|
+
const messages = this.getFormValidationMessages()
|
|
257
|
+
if (!messages.length) return
|
|
258
|
+
|
|
259
|
+
event?.preventDefault?.()
|
|
260
|
+
this.showFeedback(
|
|
261
|
+
'Please check your message',
|
|
262
|
+
'Correct the following fields before sending:',
|
|
263
|
+
messages
|
|
264
|
+
)
|
|
265
|
+
},
|
|
183
266
|
composeEmail() {
|
|
184
267
|
if (!this.recipientId) return
|
|
185
268
|
this.emailMessage = {
|
|
@@ -198,7 +281,16 @@ export default {
|
|
|
198
281
|
resetSendState() {
|
|
199
282
|
this.sending = false
|
|
200
283
|
this.sendSucceeded = false
|
|
201
|
-
this.
|
|
284
|
+
this.dismissFeedbackModal()
|
|
285
|
+
},
|
|
286
|
+
dismissFeedbackModal() {
|
|
287
|
+
this.feedbackModalOpen = false
|
|
288
|
+
},
|
|
289
|
+
showFeedback(title, introduction, messages = []) {
|
|
290
|
+
this.feedbackTitle = title
|
|
291
|
+
this.feedbackIntroduction = introduction
|
|
292
|
+
this.feedbackMessages = messages
|
|
293
|
+
this.feedbackModalOpen = true
|
|
202
294
|
},
|
|
203
295
|
waitForSuccessMessage() {
|
|
204
296
|
return new Promise(resolve => {
|
|
@@ -237,8 +329,64 @@ export default {
|
|
|
237
329
|
this.getSendEmailResponseData(error)?.code === 'moderation_rejected'
|
|
238
330
|
)
|
|
239
331
|
},
|
|
332
|
+
getServerValidationMessages(error) {
|
|
333
|
+
if (this.getSendEmailErrorStatus(error) !== 400) return []
|
|
334
|
+
|
|
335
|
+
const errorMessage = this.getSendEmailResponseData(error)?.error
|
|
336
|
+
const messages = {
|
|
337
|
+
'senderName is required.': 'Your full name is required.',
|
|
338
|
+
'senderEmail must be a valid email address.':
|
|
339
|
+
'Enter a valid email address.',
|
|
340
|
+
'messageBody is required.': 'A message is required.',
|
|
341
|
+
'messageBody is too short.':
|
|
342
|
+
'The message must be at least 5 characters.',
|
|
343
|
+
'messageBody is too long.':
|
|
344
|
+
'The message must be 8,000 characters or fewer.',
|
|
345
|
+
'Recipient was not found or does not have a public email address.':
|
|
346
|
+
'The selected recipient is no longer available for public email.',
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return [
|
|
350
|
+
messages[errorMessage] ||
|
|
351
|
+
'Some information was not accepted. Review each field and try again.',
|
|
352
|
+
]
|
|
353
|
+
},
|
|
354
|
+
showSendFailure(error) {
|
|
355
|
+
const validationMessages = this.getServerValidationMessages(error)
|
|
356
|
+
if (validationMessages.length) {
|
|
357
|
+
this.showFeedback(
|
|
358
|
+
'Please check your message',
|
|
359
|
+
'Correct the following fields before sending:',
|
|
360
|
+
validationMessages
|
|
361
|
+
)
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (this.isModerationRejection(error)) {
|
|
366
|
+
this.showFeedback(
|
|
367
|
+
'Message not sent',
|
|
368
|
+
"Your message couldn't be sent through this form.",
|
|
369
|
+
[]
|
|
370
|
+
)
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
this.showFeedback(
|
|
375
|
+
'Email not sent',
|
|
376
|
+
this.getSendEmailFailureMessage(error),
|
|
377
|
+
[]
|
|
378
|
+
)
|
|
379
|
+
},
|
|
240
380
|
async sendEmail(captchaToken = '') {
|
|
241
|
-
|
|
381
|
+
const validationMessages = this.getFormValidationMessages()
|
|
382
|
+
if (validationMessages.length) {
|
|
383
|
+
this.showFeedback(
|
|
384
|
+
'Please check your message',
|
|
385
|
+
'Correct the following fields before sending:',
|
|
386
|
+
validationMessages
|
|
387
|
+
)
|
|
388
|
+
return
|
|
389
|
+
}
|
|
242
390
|
|
|
243
391
|
const endpoint = this.resolveSendEmailEndpoint()
|
|
244
392
|
const sendEmailClient =
|
|
@@ -247,8 +395,10 @@ export default {
|
|
|
247
395
|
|
|
248
396
|
if (!sendEmailClient) {
|
|
249
397
|
console.error('Send email endpoint is not configured.')
|
|
250
|
-
|
|
251
|
-
'Email
|
|
398
|
+
this.showFeedback(
|
|
399
|
+
'Email not sent',
|
|
400
|
+
'Email sending is not configured for this environment. Please try again later.',
|
|
401
|
+
[]
|
|
252
402
|
)
|
|
253
403
|
return
|
|
254
404
|
}
|
|
@@ -269,14 +419,7 @@ export default {
|
|
|
269
419
|
} catch (error) {
|
|
270
420
|
this.sendSucceeded = false
|
|
271
421
|
console.error('Email send failed:', error?.message || error)
|
|
272
|
-
|
|
273
|
-
this.sending = false
|
|
274
|
-
this.sendRejected = true
|
|
275
|
-
await this.waitForSuccessMessage()
|
|
276
|
-
this.$emit('dismiss')
|
|
277
|
-
return
|
|
278
|
-
}
|
|
279
|
-
alert(this.getSendEmailFailureMessage(error))
|
|
422
|
+
this.showSendFailure(error)
|
|
280
423
|
} finally {
|
|
281
424
|
this.sending = false
|
|
282
425
|
}
|
|
@@ -305,6 +448,22 @@ export default {
|
|
|
305
448
|
margin: 0;
|
|
306
449
|
text-align: center;
|
|
307
450
|
}
|
|
451
|
+
|
|
452
|
+
&__feedback {
|
|
453
|
+
padding: space(1);
|
|
454
|
+
|
|
455
|
+
p {
|
|
456
|
+
margin-top: 0;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
ul {
|
|
460
|
+
margin-bottom: 0;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
&__feedback-modal :deep(.asd20-modal__footer) {
|
|
465
|
+
padding: space(0.5);
|
|
466
|
+
}
|
|
308
467
|
}
|
|
309
468
|
|
|
310
469
|
@media (min-width: 1024px) {
|