@asd20/ui-next 2.5.0 → 2.7.0
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,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
# [2.7.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.6.0...ui-next-v2.7.0) (2026-05-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* implement AI evaluation of email messages ([4ed90b4](https://github.com/academydistrict20/asd20-ui-next/commit/4ed90b413da2b2690b3655c1c701003cbdbb7bd6))
|
|
9
|
+
|
|
10
|
+
# [2.6.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.5.0...ui-next-v2.6.0) (2026-05-04)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* add rate limit to emails and associated messaging ([66e4099](https://github.com/academydistrict20/asd20-ui-next/commit/66e40994ac924a9dad184227beae4dded0dbf2d9))
|
|
16
|
+
|
|
3
17
|
# [2.5.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.4.3...ui-next-v2.5.0) (2026-05-04)
|
|
4
18
|
|
|
5
19
|
|
package/package.json
CHANGED
|
@@ -9,47 +9,91 @@
|
|
|
9
9
|
@dismiss="$emit('dismiss')"
|
|
10
10
|
>
|
|
11
11
|
<Asd20Viewport scrollable>
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<asd20-text-area-input
|
|
29
|
-
id="messageBody"
|
|
30
|
-
v-model="emailMessage.messageBody"
|
|
31
|
-
label="Message"
|
|
32
|
-
required
|
|
33
|
-
@validated="validationErrors.messageBody = $event"
|
|
34
|
-
/>
|
|
35
|
-
<Recaptcha
|
|
36
|
-
sitekey="6LfidKoUAAAAAFqr3QEbia3jIkecsZyxBYlMvWrX"
|
|
37
|
-
:load-recaptcha-script="true"
|
|
38
|
-
size="invisible"
|
|
39
|
-
@verify="captchaVerified"
|
|
40
|
-
>
|
|
41
|
-
<asd20-button
|
|
42
|
-
:disabled="!isValid || sending"
|
|
43
|
-
label="Send"
|
|
44
|
-
horizontal
|
|
45
|
-
centered
|
|
46
|
-
bordered
|
|
12
|
+
<template v-if="!sendRejected">
|
|
13
|
+
<asd20-text-input
|
|
14
|
+
id="senderName"
|
|
15
|
+
v-model="emailMessage.senderName"
|
|
16
|
+
label="Your Full Name"
|
|
17
|
+
required
|
|
18
|
+
@validated="validationErrors.senderName = $event"
|
|
19
|
+
/>
|
|
20
|
+
<asd20-text-input
|
|
21
|
+
id="senderEmail"
|
|
22
|
+
v-model="emailMessage.senderEmail"
|
|
23
|
+
type="email"
|
|
24
|
+
:validator="validateEmailAddress"
|
|
25
|
+
label="Your Email Address"
|
|
26
|
+
required
|
|
27
|
+
@validated="validationErrors.senderEmail = $event"
|
|
47
28
|
/>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
29
|
+
<asd20-text-area-input
|
|
30
|
+
id="messageBody"
|
|
31
|
+
v-model="emailMessage.messageBody"
|
|
32
|
+
label="Message"
|
|
33
|
+
required
|
|
34
|
+
@validated="validationErrors.messageBody = $event"
|
|
35
|
+
/>
|
|
36
|
+
<div
|
|
37
|
+
class="asd20-compose-email-modal__submit"
|
|
38
|
+
aria-live="polite"
|
|
39
|
+
>
|
|
40
|
+
<Recaptcha
|
|
41
|
+
v-if="!sending && !sendSucceeded"
|
|
42
|
+
sitekey="6LfidKoUAAAAAFqr3QEbia3jIkecsZyxBYlMvWrX"
|
|
43
|
+
:load-recaptcha-script="true"
|
|
44
|
+
size="invisible"
|
|
45
|
+
@verify="captchaVerified"
|
|
46
|
+
>
|
|
47
|
+
<asd20-button
|
|
48
|
+
:disabled="!isValid"
|
|
49
|
+
label="Send"
|
|
50
|
+
horizontal
|
|
51
|
+
centered
|
|
52
|
+
bordered
|
|
53
|
+
/>
|
|
54
|
+
</Recaptcha>
|
|
55
|
+
<asd20-spinner
|
|
56
|
+
v-else-if="sending"
|
|
57
|
+
size="sm"
|
|
58
|
+
/>
|
|
59
|
+
<p
|
|
60
|
+
v-else
|
|
61
|
+
class="asd20-compose-email-modal__success"
|
|
62
|
+
role="status"
|
|
63
|
+
>
|
|
64
|
+
Your message was sent.
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
</template>
|
|
68
|
+
<div
|
|
69
|
+
v-else
|
|
70
|
+
class="asd20-compose-email-modal__rejection"
|
|
71
|
+
role="status"
|
|
72
|
+
>
|
|
73
|
+
<p
|
|
74
|
+
class="asd20-compose-email-modal__rejection-message"
|
|
75
|
+
>
|
|
76
|
+
Please contact the Academy District 20 Help Desk who can evaluate your
|
|
77
|
+
request and route it appropriately.
|
|
78
|
+
</p>
|
|
79
|
+
<div class="asd20-compose-email-modal__rejection-actions">
|
|
80
|
+
<asd20-button
|
|
81
|
+
label="Contact Our Help Desk"
|
|
82
|
+
:link="helpDeskUrl"
|
|
83
|
+
horizontal
|
|
84
|
+
centered
|
|
85
|
+
bordered
|
|
86
|
+
/>
|
|
87
|
+
<asd20-button
|
|
88
|
+
label="Cancel"
|
|
89
|
+
horizontal
|
|
90
|
+
centered
|
|
91
|
+
bordered
|
|
92
|
+
transparent
|
|
93
|
+
@click="$emit('dismiss')"
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
53
97
|
</Asd20Viewport>
|
|
54
98
|
</asd20-modal>
|
|
55
99
|
</template>
|
|
@@ -87,6 +131,9 @@ export default {
|
|
|
87
131
|
},
|
|
88
132
|
composing: false,
|
|
89
133
|
sending: false,
|
|
134
|
+
sendSucceeded: false,
|
|
135
|
+
sendRejected: false,
|
|
136
|
+
helpDeskUrl: 'https://asd20.org/help-desk',
|
|
90
137
|
emailMessage: {
|
|
91
138
|
senderName: '',
|
|
92
139
|
senderEmail: '',
|
|
@@ -102,6 +149,11 @@ export default {
|
|
|
102
149
|
)
|
|
103
150
|
},
|
|
104
151
|
},
|
|
152
|
+
watch: {
|
|
153
|
+
open(value) {
|
|
154
|
+
if (value) this.resetSendState()
|
|
155
|
+
},
|
|
156
|
+
},
|
|
105
157
|
methods: {
|
|
106
158
|
resolveRuntimeConfig() {
|
|
107
159
|
const runtimeConfig =
|
|
@@ -129,7 +181,8 @@ export default {
|
|
|
129
181
|
)
|
|
130
182
|
},
|
|
131
183
|
validateEmailAddress({ value, validationErrors }) {
|
|
132
|
-
const pattern =
|
|
184
|
+
const pattern =
|
|
185
|
+
/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
|
|
133
186
|
const regex = RegExp(pattern)
|
|
134
187
|
if (!regex.test(value))
|
|
135
188
|
validationErrors.push('A valid email address is required')
|
|
@@ -149,15 +202,40 @@ export default {
|
|
|
149
202
|
captchaVerified(response) {
|
|
150
203
|
if (response) this.sendEmail(response)
|
|
151
204
|
},
|
|
205
|
+
resetSendState() {
|
|
206
|
+
this.sending = false
|
|
207
|
+
this.sendSucceeded = false
|
|
208
|
+
this.sendRejected = false
|
|
209
|
+
},
|
|
210
|
+
waitForSuccessMessage() {
|
|
211
|
+
return new Promise(resolve => {
|
|
212
|
+
setTimeout(resolve, 1200)
|
|
213
|
+
})
|
|
214
|
+
},
|
|
215
|
+
getSendEmailErrorStatus(error) {
|
|
216
|
+
if (error?.response?.status) return Number(error.response.status)
|
|
217
|
+
if (error?.status) return Number(error.status)
|
|
218
|
+
|
|
219
|
+
const statusMatch = String(error?.message || '').match(/\b(\d{3})\b/)
|
|
220
|
+
return statusMatch ? Number(statusMatch[1]) : null
|
|
221
|
+
},
|
|
222
|
+
getSendEmailFailureMessage(error) {
|
|
223
|
+
if (this.getSendEmailErrorStatus(error) === 429) {
|
|
224
|
+
return 'We could not send your message right now. Please wait and try again later.'
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return 'Something went wrong while sending your email. Please try again later.'
|
|
228
|
+
},
|
|
229
|
+
isHelpDeskRejection(error) {
|
|
230
|
+
return error?.response?.data?.code === 'moderation_rejected'
|
|
231
|
+
},
|
|
152
232
|
async sendEmail(captchaToken = '') {
|
|
153
233
|
if (!this.isValid) return
|
|
154
234
|
|
|
155
235
|
const endpoint = this.resolveSendEmailEndpoint()
|
|
156
236
|
const sendEmailClient =
|
|
157
237
|
this.$sendEmail ||
|
|
158
|
-
(endpoint
|
|
159
|
-
? async (message) => await sendEmail(message, endpoint)
|
|
160
|
-
: null)
|
|
238
|
+
(endpoint ? async message => await sendEmail(message, endpoint) : null)
|
|
161
239
|
|
|
162
240
|
if (!sendEmailClient) {
|
|
163
241
|
console.error('Send email endpoint is not configured.')
|
|
@@ -168,6 +246,7 @@ export default {
|
|
|
168
246
|
}
|
|
169
247
|
|
|
170
248
|
this.sending = true
|
|
249
|
+
this.sendSucceeded = false
|
|
171
250
|
try {
|
|
172
251
|
await sendEmailClient(
|
|
173
252
|
Object.assign({}, this.emailMessage, {
|
|
@@ -175,12 +254,20 @@ export default {
|
|
|
175
254
|
captchaToken,
|
|
176
255
|
})
|
|
177
256
|
)
|
|
257
|
+
this.sending = false
|
|
258
|
+
this.sendSucceeded = true
|
|
259
|
+
await this.waitForSuccessMessage()
|
|
178
260
|
this.$emit('dismiss')
|
|
179
261
|
} catch (error) {
|
|
262
|
+
this.sendSucceeded = false
|
|
180
263
|
console.error('Email send failed:', error?.message || error)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
264
|
+
if (this.isHelpDeskRejection(error)) {
|
|
265
|
+
this.helpDeskUrl =
|
|
266
|
+
error?.response?.data?.helpDeskUrl || 'https://asd20.org/help-desk'
|
|
267
|
+
this.sendRejected = true
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
alert(this.getSendEmailFailureMessage(error))
|
|
184
271
|
} finally {
|
|
185
272
|
this.sending = false
|
|
186
273
|
}
|
|
@@ -196,6 +283,35 @@ export default {
|
|
|
196
283
|
& :deep(.asd20-modal__content .asd20-viewport) {
|
|
197
284
|
padding: space(1);
|
|
198
285
|
}
|
|
286
|
+
|
|
287
|
+
&__submit {
|
|
288
|
+
align-items: center;
|
|
289
|
+
display: flex;
|
|
290
|
+
justify-content: center;
|
|
291
|
+
min-height: 3rem;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
&__success {
|
|
295
|
+
font-weight: 600;
|
|
296
|
+
margin: 0;
|
|
297
|
+
text-align: center;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
&__rejection {
|
|
301
|
+
display: flex;
|
|
302
|
+
flex-direction: column;
|
|
303
|
+
gap: space(1);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
&__rejection-message {
|
|
307
|
+
margin: 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
&__rejection-actions {
|
|
311
|
+
display: flex;
|
|
312
|
+
flex-wrap: wrap;
|
|
313
|
+
gap: space(0.75);
|
|
314
|
+
}
|
|
199
315
|
}
|
|
200
316
|
|
|
201
317
|
@media (min-width: 1024px) {
|
package/src/helpers/sendEmail.js
CHANGED
|
@@ -9,7 +9,7 @@ function emailMessageIsValid(emailMessage) {
|
|
|
9
9
|
)
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export default async function(emailMessage, endpoint) {
|
|
12
|
+
export default async function (emailMessage, endpoint) {
|
|
13
13
|
// Check to see if the parameters is valid
|
|
14
14
|
if (!emailMessageIsValid(emailMessage))
|
|
15
15
|
throw new Error(
|
|
@@ -20,13 +20,15 @@ export default async function(emailMessage, endpoint) {
|
|
|
20
20
|
await axios.post(endpoint, emailMessage)
|
|
21
21
|
} catch (error) {
|
|
22
22
|
if (error.response) {
|
|
23
|
-
|
|
23
|
+
const sendError = new Error(
|
|
24
24
|
`A ${
|
|
25
25
|
error.response.status
|
|
26
26
|
} error occurred while attempting to send email: ${JSON.stringify(
|
|
27
27
|
error.response.data
|
|
28
28
|
)}`
|
|
29
29
|
)
|
|
30
|
+
sendError.response = error.response
|
|
31
|
+
throw sendError
|
|
30
32
|
} else {
|
|
31
33
|
// Something happened in setting up the request and triggered an Error
|
|
32
34
|
throw new Error(
|