@asd20/ui-next 2.4.3 → 2.6.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.6.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.5.0...ui-next-v2.6.0) (2026-05-04)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add rate limit to emails and associated messaging ([66e4099](https://github.com/academydistrict20/asd20-ui-next/commit/66e40994ac924a9dad184227beae4dded0dbf2d9))
|
|
9
|
+
|
|
10
|
+
# [2.5.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.4.3...ui-next-v2.5.0) (2026-05-04)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* Add public directory email abuse controls ([9ae4256](https://github.com/academydistrict20/asd20-ui-next/commit/9ae4256f3461a205b82ea0a6c46a4d3190aeff87))
|
|
16
|
+
|
|
3
17
|
## [2.4.3](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.4.2...ui-next-v2.4.3) (2026-05-04)
|
|
4
18
|
|
|
5
19
|
|
package/package.json
CHANGED
|
@@ -32,24 +32,37 @@
|
|
|
32
32
|
required
|
|
33
33
|
@validated="validationErrors.messageBody = $event"
|
|
34
34
|
/>
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
size="invisible"
|
|
39
|
-
@verify="captchaVerified"
|
|
35
|
+
<div
|
|
36
|
+
class="asd20-compose-email-modal__submit"
|
|
37
|
+
aria-live="polite"
|
|
40
38
|
>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
<Recaptcha
|
|
40
|
+
v-if="!sending && !sendSucceeded"
|
|
41
|
+
sitekey="6LfidKoUAAAAAFqr3QEbia3jIkecsZyxBYlMvWrX"
|
|
42
|
+
:load-recaptcha-script="true"
|
|
43
|
+
size="invisible"
|
|
44
|
+
@verify="captchaVerified"
|
|
45
|
+
>
|
|
46
|
+
<asd20-button
|
|
47
|
+
:disabled="!isValid"
|
|
48
|
+
label="Send"
|
|
49
|
+
horizontal
|
|
50
|
+
centered
|
|
51
|
+
bordered
|
|
52
|
+
/>
|
|
53
|
+
</Recaptcha>
|
|
54
|
+
<asd20-spinner
|
|
55
|
+
v-else-if="sending"
|
|
56
|
+
size="sm"
|
|
47
57
|
/>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
<p
|
|
59
|
+
v-else
|
|
60
|
+
class="asd20-compose-email-modal__success"
|
|
61
|
+
role="status"
|
|
62
|
+
>
|
|
63
|
+
Your message was sent.
|
|
64
|
+
</p>
|
|
65
|
+
</div>
|
|
53
66
|
</Asd20Viewport>
|
|
54
67
|
</asd20-modal>
|
|
55
68
|
</template>
|
|
@@ -87,6 +100,7 @@ export default {
|
|
|
87
100
|
},
|
|
88
101
|
composing: false,
|
|
89
102
|
sending: false,
|
|
103
|
+
sendSucceeded: false,
|
|
90
104
|
emailMessage: {
|
|
91
105
|
senderName: '',
|
|
92
106
|
senderEmail: '',
|
|
@@ -102,6 +116,11 @@ export default {
|
|
|
102
116
|
)
|
|
103
117
|
},
|
|
104
118
|
},
|
|
119
|
+
watch: {
|
|
120
|
+
open(value) {
|
|
121
|
+
if (value) this.resetSendState()
|
|
122
|
+
},
|
|
123
|
+
},
|
|
105
124
|
methods: {
|
|
106
125
|
resolveRuntimeConfig() {
|
|
107
126
|
const runtimeConfig =
|
|
@@ -129,7 +148,8 @@ export default {
|
|
|
129
148
|
)
|
|
130
149
|
},
|
|
131
150
|
validateEmailAddress({ value, validationErrors }) {
|
|
132
|
-
const pattern =
|
|
151
|
+
const pattern =
|
|
152
|
+
/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
|
|
133
153
|
const regex = RegExp(pattern)
|
|
134
154
|
if (!regex.test(value))
|
|
135
155
|
validationErrors.push('A valid email address is required')
|
|
@@ -147,17 +167,38 @@ export default {
|
|
|
147
167
|
this.validInputs += errors.length === 0 ? -1 : 1
|
|
148
168
|
},
|
|
149
169
|
captchaVerified(response) {
|
|
150
|
-
if (response) this.sendEmail()
|
|
170
|
+
if (response) this.sendEmail(response)
|
|
171
|
+
},
|
|
172
|
+
resetSendState() {
|
|
173
|
+
this.sending = false
|
|
174
|
+
this.sendSucceeded = false
|
|
175
|
+
},
|
|
176
|
+
waitForSuccessMessage() {
|
|
177
|
+
return new Promise(resolve => {
|
|
178
|
+
setTimeout(resolve, 1200)
|
|
179
|
+
})
|
|
151
180
|
},
|
|
152
|
-
|
|
181
|
+
getSendEmailErrorStatus(error) {
|
|
182
|
+
if (error?.response?.status) return Number(error.response.status)
|
|
183
|
+
if (error?.status) return Number(error.status)
|
|
184
|
+
|
|
185
|
+
const statusMatch = String(error?.message || '').match(/\b(\d{3})\b/)
|
|
186
|
+
return statusMatch ? Number(statusMatch[1]) : null
|
|
187
|
+
},
|
|
188
|
+
getSendEmailFailureMessage(error) {
|
|
189
|
+
if (this.getSendEmailErrorStatus(error) === 429) {
|
|
190
|
+
return 'We could not send your message right now. Please wait and try again later.'
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return 'Something went wrong while sending your email. Please try again later.'
|
|
194
|
+
},
|
|
195
|
+
async sendEmail(captchaToken = '') {
|
|
153
196
|
if (!this.isValid) return
|
|
154
197
|
|
|
155
198
|
const endpoint = this.resolveSendEmailEndpoint()
|
|
156
199
|
const sendEmailClient =
|
|
157
200
|
this.$sendEmail ||
|
|
158
|
-
(endpoint
|
|
159
|
-
? async (message) => await sendEmail(message, endpoint)
|
|
160
|
-
: null)
|
|
201
|
+
(endpoint ? async message => await sendEmail(message, endpoint) : null)
|
|
161
202
|
|
|
162
203
|
if (!sendEmailClient) {
|
|
163
204
|
console.error('Send email endpoint is not configured.')
|
|
@@ -168,18 +209,22 @@ export default {
|
|
|
168
209
|
}
|
|
169
210
|
|
|
170
211
|
this.sending = true
|
|
212
|
+
this.sendSucceeded = false
|
|
171
213
|
try {
|
|
172
214
|
await sendEmailClient(
|
|
173
215
|
Object.assign({}, this.emailMessage, {
|
|
174
216
|
recipientId: this.recipientId,
|
|
217
|
+
captchaToken,
|
|
175
218
|
})
|
|
176
219
|
)
|
|
220
|
+
this.sending = false
|
|
221
|
+
this.sendSucceeded = true
|
|
222
|
+
await this.waitForSuccessMessage()
|
|
177
223
|
this.$emit('dismiss')
|
|
178
224
|
} catch (error) {
|
|
225
|
+
this.sendSucceeded = false
|
|
179
226
|
console.error('Email send failed:', error?.message || error)
|
|
180
|
-
alert(
|
|
181
|
-
'Something went wrong while sending your email. Please try again later.'
|
|
182
|
-
)
|
|
227
|
+
alert(this.getSendEmailFailureMessage(error))
|
|
183
228
|
} finally {
|
|
184
229
|
this.sending = false
|
|
185
230
|
}
|
|
@@ -195,6 +240,19 @@ export default {
|
|
|
195
240
|
& :deep(.asd20-modal__content .asd20-viewport) {
|
|
196
241
|
padding: space(1);
|
|
197
242
|
}
|
|
243
|
+
|
|
244
|
+
&__submit {
|
|
245
|
+
align-items: center;
|
|
246
|
+
display: flex;
|
|
247
|
+
justify-content: center;
|
|
248
|
+
min-height: 3rem;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
&__success {
|
|
252
|
+
font-weight: 600;
|
|
253
|
+
margin: 0;
|
|
254
|
+
text-align: center;
|
|
255
|
+
}
|
|
198
256
|
}
|
|
199
257
|
|
|
200
258
|
@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(
|