@asd20/ui 3.2.977 → 3.2.978

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/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "*.scss",
6
6
  "*.vue"
7
7
  ],
8
- "version": "3.2.977",
8
+ "version": "3.2.978",
9
9
  "private": false,
10
10
  "license": "MIT",
11
11
  "repository": {
@@ -108,8 +108,7 @@ storiesOf('Atoms - Asd20Icon', module)
108
108
  'youtube',
109
109
  'twitter',
110
110
  'facebook',
111
-
112
- 'accessiblity',
111
+ 'accessibility',
113
112
  'alert',
114
113
  'award',
115
114
  'bell',
@@ -0,0 +1,20 @@
1
+ import { storiesOf } from '@storybook/vue'
2
+ import Asd20AccessibilityIssueModal from '.'
3
+
4
+ const info = {
5
+ summary: 'Asd20AccessibilityIssueModal',
6
+ }
7
+
8
+ const wrapper = {
9
+ components: { Asd20AccessibilityIssueModal },
10
+ data: () => ({}),
11
+ }
12
+
13
+ storiesOf('Organisms - Asd20AccessibilityIssueModal', module).add(
14
+ 'Default',
15
+ () => ({
16
+ ...wrapper,
17
+ template: `<Asd20AccessibilityIssueModal :open="true" recipient-id="14776"></Asd20AccessibilityIssueModal>`,
18
+ }),
19
+ { info }
20
+ )
@@ -0,0 +1,211 @@
1
+ <template>
2
+ <asd20-modal
3
+ :open="open"
4
+ title="Report an Accessibility Issue"
5
+ icon="accessibility"
6
+ dismissable
7
+ windowed
8
+ class="asd20-accessibility-issue-modal"
9
+ @dismiss="$emit('dismiss')"
10
+ >
11
+ <Asd20Viewport scrollable>
12
+ <div v-if="submissionSuccess" class="submission-success">
13
+ <p style="text-align: center; font-weight: bold; font-size: 1.1rem;">
14
+ Thank you! Your issue has been submitted.
15
+ </p>
16
+ </div>
17
+
18
+ <template v-else>
19
+ <asd20-text-input
20
+ id="firstName"
21
+ label="First Name"
22
+ required
23
+ v-model="form.firstName"
24
+ @validated="validationErrors.firstName = $event"
25
+ />
26
+ <asd20-text-input
27
+ id="lastName"
28
+ label="Last Name"
29
+ required
30
+ v-model="form.lastName"
31
+ @validated="validationErrors.lastName = $event"
32
+ />
33
+ <asd20-text-input
34
+ id="email"
35
+ label="Email Address"
36
+ type="email"
37
+ required
38
+ :validator="validateEmailAddress"
39
+ v-model="form.email"
40
+ @validated="validationErrors.email = $event"
41
+ />
42
+ <asd20-text-input
43
+ id="phone"
44
+ label="Phone Number"
45
+ v-model="form.phone"
46
+ />
47
+ <asd20-text-input
48
+ id="address"
49
+ label="Mailing Address"
50
+ v-model="form.address"
51
+ />
52
+ <asd20-text-input
53
+ id="organization"
54
+ label="Organization"
55
+ v-model="form.organization"
56
+ />
57
+ <asd20-text-area-input
58
+ id="issue"
59
+ label="Describe the Issue"
60
+ help-text="<ul style='margin: 0 0 0.5rem 1rem; font-size: 0.75rem !important;'>
61
+ <li>Please specify the exact URL, program, service, or digital asset you had a problem with.</li>
62
+ <li>Please provide as much detail as possible, including the nature of the accessibility barrier, the date and time it occurred, any assistive technology you were using, or the nature of the program or service being denied.</li>
63
+ <li>Please describe what you believe should be done to resolve the issue.</li>
64
+ </ul>"
65
+ required
66
+ v-model="form.issue"
67
+ @validated="validationErrors.issue = $event"
68
+ />
69
+ <asd20-button
70
+ label="Submit Issue"
71
+ :disabled="!isValid || sending"
72
+ @click="sendAccessibilityIssue"
73
+ horizontal
74
+ centered
75
+ bordered
76
+ />
77
+ <!-- <vue-recaptcha
78
+ sitekey="6LfidKoUAAAAAFqr3QEbia3jIkecsZyxBYlMvWrX"
79
+ :loadRecaptchaScript="true"
80
+ size="invisible"
81
+ @verify="captchaVerified"
82
+ >
83
+ <asd20-button
84
+ v-show="isValid && !sending"
85
+ label="Submit Issue"
86
+ horizontal
87
+ />
88
+ </vue-recaptcha> -->
89
+ <asd20-spinner size="sm" v-if="sending" />
90
+ </template>
91
+ </Asd20Viewport>
92
+ </asd20-modal>
93
+ </template>
94
+
95
+ <script>
96
+ import Asd20Modal from '../../molecules/Asd20Modal'
97
+ import Asd20TextInput from '../../molecules/Asd20TextInput'
98
+ import Asd20TextAreaInput from '../../molecules/Asd20TextAreaInput'
99
+ import Asd20Viewport from '../../atoms/Asd20Viewport'
100
+ import Asd20Spinner from '../../atoms/Asd20Spinner'
101
+ import VueRecaptcha from 'vue-recaptcha'
102
+ import Asd20Button from '../../atoms/Asd20Button'
103
+
104
+ // import sendAccessibilityIssue from '../../../helpers/sendAccessibilityIssue.js'
105
+
106
+ export default {
107
+ name: 'Asd20AccessibilityIssueModal',
108
+ components: {
109
+ Asd20Modal,
110
+ Asd20TextInput,
111
+ Asd20TextAreaInput,
112
+ Asd20Viewport,
113
+ Asd20Spinner,
114
+ VueRecaptcha,
115
+ Asd20Button,
116
+ },
117
+ props: {
118
+ open: { type: Boolean, default: false },
119
+ },
120
+ data() {
121
+ return {
122
+ sending: false,
123
+ submissionSuccess: false,
124
+ form: {
125
+ firstName: '',
126
+ lastName: '',
127
+ email: '',
128
+ phone: '',
129
+ address: '',
130
+ organization: '',
131
+ issue: '',
132
+ },
133
+ validationErrors: {
134
+ firstName: [],
135
+ lastName: [],
136
+ email: [],
137
+ issue: [],
138
+ },
139
+ }
140
+ },
141
+ computed: {
142
+ isValid() {
143
+ return Object.values(this.validationErrors).every(e => e.length === 0)
144
+ },
145
+ },
146
+ methods: {
147
+ validateEmailAddress({ value, validationErrors }) {
148
+ const pattern = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
149
+ if (!pattern.test(value)) {
150
+ validationErrors.push('A valid email address is required')
151
+ }
152
+ return validationErrors
153
+ },
154
+ captchaVerified(token) {
155
+ // Placeholder: reCAPTCHA not functional yet
156
+ this.submitIssue()
157
+ },
158
+ async sendAccessibilityIssue() {
159
+ if (!this.isValid || !this.$sendAccessibilityIssue) return
160
+ this.sending = true
161
+
162
+ try {
163
+ await this.$sendAccessibilityIssue(
164
+ this.form,
165
+ this.$config.happyfoxEndpoint,
166
+ this.$config.happyfoxApiKey,
167
+ this.$config.happyfoxAuthCode
168
+ )
169
+ this.submissionSuccess = true
170
+
171
+ // Wait 2 seconds, then close the modal
172
+ setTimeout(() => {
173
+ this.submissionSuccess = false
174
+ this.$emit('dismiss')
175
+ }, 2000)
176
+ } catch (e) {
177
+ console.error('Accessibility issue submission failed:', e.message || e)
178
+ alert(
179
+ 'Something went wrong while submitting the form. Please try again later.'
180
+ )
181
+ } finally {
182
+ this.sending = false
183
+ }
184
+ },
185
+ },
186
+ }
187
+ </script>
188
+
189
+ <style lang="scss" scoped>
190
+ @import '../../../design/_mixins.scss';
191
+ @import '../../../design/_variables.scss';
192
+
193
+ .asd20-accessibility-issue-modal {
194
+ &::v-deep .asd20-modal__content .asd20-viewport {
195
+ padding: space(1);
196
+ }
197
+ }
198
+
199
+ @media (min-width: 1024px) {
200
+ .asd20-accessibility-issue-modal {
201
+ &::v-deep .asd20-modal__content .asd20-viewport {
202
+ padding: space(1);
203
+ }
204
+ &::v-deep .asd20-modal {
205
+ margin-top: auto;
206
+ margin-bottom: auto;
207
+ max-width: 40vw;
208
+ }
209
+ }
210
+ }
211
+ </style>
@@ -240,6 +240,7 @@ export default {
240
240
  // flex: 1 1 var(--minimum-column-width);
241
241
  // margin: var(--gutter) !important;
242
242
  max-width: 45%;
243
+ flex-grow: 1;
243
244
  }
244
245
  }
245
246
  }
@@ -0,0 +1,68 @@
1
+ import { storiesOf } from '@storybook/vue'
2
+ import { withKnobs, boolean } from '@storybook/addon-knobs'
3
+
4
+ import mockPageMixin from '../../../mixins/mockPageMixin'
5
+
6
+ import Asd20WayfindingAccessibilityTemplate from '.'
7
+ import pageQueryResult from '../../../data/page-queries/landing-page-query-result.json'
8
+ import notifications from '../../../data/messages/notifications'
9
+
10
+ const info = {
11
+ summary: 'Wayfinding Accessibility Page Template',
12
+ }
13
+
14
+ const wrapper = {
15
+ mixins: [mockPageMixin],
16
+ components: {
17
+ Asd20WayfindingAccessibilityTemplate,
18
+ },
19
+ watch: {
20
+ languageCode(newValue, oldValue) {
21
+ console.log(newValue, oldValue)
22
+ },
23
+ },
24
+
25
+ data: () => ({ pageQueryResult, languageCode: 'es' }),
26
+
27
+ computed: {
28
+ activeNotificationsByType() {
29
+ const empty = {
30
+ inline: [],
31
+ banner: [],
32
+ floating: [],
33
+ status: [],
34
+ }
35
+ return boolean('Show Notifications', true)
36
+ ? notifications || empty
37
+ : empty
38
+ },
39
+ },
40
+
41
+ }
42
+
43
+ const stories = storiesOf(
44
+ 'Templates - Asd20WayfindingAccessibilityTemplate',
45
+ module
46
+ )
47
+ stories.addDecorator(withKnobs)
48
+
49
+ stories.add(
50
+ 'Default',
51
+ () => ({
52
+ ...wrapper,
53
+ template: `
54
+ <Asd20WayfindingAccessibilityTemplate
55
+ v-bind="templateProps"
56
+ :menu-open.sync="menuOpen"
57
+ :search-open.sync="searchOpen"
58
+
59
+ @announcements-in-view="loadAnnouncements"
60
+ @stories-in-view="loadStories"
61
+ @events-in-view="loadEvents"
62
+ @files-in-view="loadFiles"
63
+ @people-in-view="loadPeople"
64
+ :languageCode.sync="languageCode"
65
+ />`,
66
+ }),
67
+ { info }
68
+ )
@@ -0,0 +1,337 @@
1
+ <template>
2
+ <div class="asd20-template-wayfinding-accessibility">
3
+ <!-- Skip Nav -->
4
+ <asd20-skip-to></asd20-skip-to>
5
+
6
+ <!-- Site Navigation -->
7
+ <asd20-site-navigation
8
+ :navigation="navigation"
9
+ :action-items="actionItems"
10
+ @update:menuOpen="$emit('update:menuOpen', $event)"
11
+ :menu-open="menuOpen"
12
+ @update:searchOpen="$emit('update:searchOpen', $event)"
13
+ :search-open="searchOpen"
14
+ :organization="organization"
15
+ :organization-options="organizationOptions"
16
+ >
17
+ </asd20-site-navigation>
18
+
19
+ <!-- Banner Notifications -->
20
+ <client-only>
21
+ <asd20-notification-group
22
+ :notifications="activeNotificationsByType.banner"
23
+ group-type="banner"
24
+ @dismiss="$emit('dismiss-notification', $event)"
25
+ @toggle-all="$emit('toggle-all')"
26
+ ></asd20-notification-group>
27
+ </client-only>
28
+
29
+ <!-- Page Header -->
30
+ <asd20-page-header
31
+ id="page-header"
32
+ v-show="pageHeaderContent"
33
+ v-bind="pageHeaderContent"
34
+ :organization="organization"
35
+ :organization-options="organizationOptions"
36
+ :languageCode="languageCode"
37
+ v-on="$listeners"
38
+ :breadcrumb-links="breadcrumbLinks"
39
+ >
40
+ <!-- Floating Notifications -->
41
+ <template slot="top">
42
+ <client-only>
43
+ <asd20-notification-group
44
+ class="asd20-notification-group--floating"
45
+ :notifications="activeNotificationsByType.floating"
46
+ :total-dismissed-notifications="totalDismissedNotifications"
47
+ group-type="floating"
48
+ @dismiss="$emit('dismiss-notification', $event)"
49
+ @toggle-all="$emit('toggle-all')"
50
+ ></asd20-notification-group>
51
+ </client-only>
52
+ </template>
53
+ <!-- <asd20-breadcrumb slot="top" :links="breadcrumbLinks"></asd20-breadcrumb> -->
54
+ </asd20-page-header>
55
+
56
+ <!-- Inline-Notifications -->
57
+ <template>
58
+ <client-only>
59
+ <asd20-notification-group
60
+ class="asd20-notification-group--inline"
61
+ :notifications="activeNotificationsByType.inline"
62
+ group-type="inline"
63
+ @dismiss="$emit('dismiss-notification', $event)"
64
+ @toggle-all="$emit('toggle-all')"
65
+ ></asd20-notification-group>
66
+ </client-only>
67
+ </template>
68
+
69
+ <!-- Page Content -->
70
+ <asd20-page-content
71
+ class="intro-message"
72
+ :primary-messages="primaryMessage"
73
+ omit-detail-links
74
+ omit-calls-to-action
75
+ />
76
+ <asd20-page-content
77
+ :class="wayFindingPrimaryMessages.length > 3 ? 'limit-message-width' : ''"
78
+ :primary-messages="wayFindingPrimaryMessages"
79
+ :secondary-messages="secondaryMessages"
80
+ omit-body-content
81
+ omit-calls-to-action
82
+ grid
83
+ />
84
+
85
+ <!-- Feed Section -->
86
+ <asd20-feeds-section
87
+ :announcements="announcements"
88
+ :announcements-feed-props="announcementsFeedProps"
89
+ @announcements-in-view="$emit('announcements-in-view')"
90
+ :stories="stories"
91
+ :stories-feed-props="storiesFeedProps"
92
+ @stories-in-view="$emit('stories-in-view')"
93
+ :events="events"
94
+ :events-feed-props="eventsFeedProps"
95
+ @events-in-view="$emit('events-in-view')"
96
+ :organization="organization"
97
+ :organization-options="organizationOptions"
98
+ ></asd20-feeds-section>
99
+
100
+ <!-- Photos & Videos -->
101
+ <asd20-media-section
102
+ v-if="firstMessage.images.length > 1 || firstMessage.videos.length"
103
+ :images="this.firstMessage.images || []"
104
+ :videos="this.firstMessage.videos || []"
105
+ >
106
+ </asd20-media-section>
107
+
108
+ <!-- Bottom Widgets -->
109
+ <asd20-widgets-section
110
+ v-if="
111
+ widgetsSectionProps.footer ||
112
+ widgetsSectionProps.sidebar ||
113
+ $slots.widgets
114
+ "
115
+ :related-links="relatedLinks"
116
+ v-bind="{ ...widgetsSectionProps.footer, ...widgetsSectionProps.sidebar }"
117
+ @events-in-view="$emit('events-in-view')"
118
+ @files-in-view="$emit('files-in-view')"
119
+ @people-in-view="$emit('people-in-view')"
120
+ full
121
+ ><slot name="widgets"
122
+ /></asd20-widgets-section>
123
+
124
+ <slot name="before-footer" />
125
+
126
+ <!-- Quick Links -->
127
+ <asd20-quicklinks-menu slot="before-footer" :quick-links="quickLinks">
128
+ <slot name="quicklinks-menu" />
129
+ </asd20-quicklinks-menu>
130
+
131
+ <!-- Footer -->
132
+ <asd20-page-footer
133
+ :organization="organization"
134
+ :socialLinks="socialLinks"
135
+ :disclosureLinks="disclosureLinks"
136
+ :websiteLogoProps="websiteLogoProps"
137
+ :websiteLogoProps2="websiteLogoProps2"
138
+ >
139
+ <slot name="page-footer" />
140
+ </asd20-page-footer>
141
+ <Asd20AccessibilityIssueModal
142
+ v-show="modalVisible"
143
+ :open="modalOpen"
144
+ @dismiss="modalOpen = false"
145
+ />
146
+ </div>
147
+ </template>
148
+
149
+ <script>
150
+ // Components
151
+ import Asd20SkipTo from '../../../components/atoms/Asd20SkipTo'
152
+ import Asd20PageHeader from '../../../components/organisms/Asd20PageHeader'
153
+
154
+ import Asd20SiteNavigation from '../../../components/organisms/Asd20SiteNavigation'
155
+ import Asd20PageContent from '../../../components/organisms/Asd20PageContent'
156
+
157
+ import Asd20WidgetsSection from '../../../components/organisms/Asd20WidgetsSection'
158
+ import Asd20FeedsSection from '../../../components/organisms/Asd20FeedsSection'
159
+ import Asd20MediaSection from '../../../components/organisms/Asd20MediaSection'
160
+ import Asd20AccessibilityIssueModal from '../../../components/organisms/Asd20AccessibilityIssueModal'
161
+ import Asd20PageFooter from '../../../components/organisms/Asd20PageFooter'
162
+ import Asd20QuicklinksMenu from '../../../components/organisms/Asd20QuicklinksMenu'
163
+ import Asd20NotificationGroup from '../../../components/organisms/Asd20NotificationGroup'
164
+
165
+ // import Intersect from 'vue-intersect'
166
+
167
+ // Mixins
168
+ import pageTemplateMixin from '../../../mixins/pageTemplateMixin'
169
+
170
+ export default {
171
+ name: 'Asd20WayfindingAccessibilityTemplate',
172
+ mixins: [pageTemplateMixin],
173
+ components: {
174
+ Asd20PageContent,
175
+ Asd20SkipTo,
176
+ Asd20PageHeader,
177
+ Asd20PageFooter,
178
+ Asd20SiteNavigation,
179
+ Asd20WidgetsSection,
180
+ Asd20FeedsSection,
181
+ Asd20NotificationGroup,
182
+ Asd20QuicklinksMenu,
183
+ Asd20MediaSection,
184
+ Asd20AccessibilityIssueModal,
185
+ },
186
+ data() {
187
+ return {
188
+ modalVisible: false,
189
+ modalOpen: false,
190
+ }
191
+ },
192
+ mounted() {
193
+ this.$nextTick(() => {
194
+ const btn = document.querySelector('[data-trigger-accessibility-modal]')
195
+ if (btn) {
196
+ btn.addEventListener('click', () => {
197
+ this.modalVisible = true
198
+ this.$nextTick(() => {
199
+ this.modalOpen = true
200
+ })
201
+ })
202
+ }
203
+ })
204
+ },
205
+ computed: {
206
+ wayFindingPrimaryMessages() {
207
+ // wayfinding page
208
+ return (
209
+ this.messages
210
+ // Remove the first message, limit
211
+ .slice(
212
+ 1,
213
+ Math.min(this.messages.length, this.primaryMessageLimit + 1)
214
+ )
215
+ )
216
+ },
217
+ primaryMessage() {
218
+ const introMessage = this.primaryMessages.slice(0, 1)
219
+ if (introMessage[0].bodyHtml === '' || null) return
220
+ return introMessage
221
+ },
222
+ },
223
+ }
224
+ </script>
225
+
226
+ <style lang="scss">
227
+ @import '../../../design/_variables.scss';
228
+ @import '../../../design/_mixins.scss';
229
+ @import '../../../design/_typography.scss';
230
+ @import '../../../design/_template.scss';
231
+ @import '../../../design/_print.scss';
232
+ @import '../../../design/tokens.css';
233
+
234
+ .asd20-template-wayfinding-accessibility {
235
+ @include typography;
236
+ @include template;
237
+ display: flex;
238
+ flex-direction: column;
239
+ flex-grow: 1;
240
+ flex-shrink: 0;
241
+ overflow: hidden;
242
+ margin-top: space(2.25);
243
+ .asd20-notification-group--floating {
244
+ position: absolute;
245
+ top: space(2.0375);
246
+ // .bell {
247
+ // box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.125) !important;
248
+ // svg {
249
+ // fill: var(--color__accent) !important;
250
+ // }
251
+ // span {
252
+ // background: var(--color__accent) !important;
253
+ // top: inherit !important;
254
+ // right: inherit !important;
255
+ // width: 1.2rem !important;
256
+ // height: 1.2rem !important;
257
+ // font-size: 0.75rem !important;
258
+ // }
259
+ // }
260
+ }
261
+ .asd20-notification-group--inline {
262
+ margin-top: space(2) !important;
263
+ }
264
+
265
+ .intro-message {
266
+ .primary-messaging-section {
267
+ justify-content: center;
268
+ margin-top: space(1) !important;
269
+ padding: space(1) space(2) !important;
270
+ .asd20-messaging {
271
+ border-top: space(0.25) solid var(--color__tertiary);
272
+ border-bottom: space(0.25) solid var(--color__tertiary);
273
+ padding: space(1) 0;
274
+ }
275
+ }
276
+
277
+ p {
278
+ // font-family: var(--website-typography__font-family-headlines);
279
+ font-size: 1rem !important;
280
+ line-height: 1.5;
281
+ font-family: var(--website-typography__font-family-headlines);
282
+ }
283
+ h2 {
284
+ @include asd20-font(
285
+ 1.5,
286
+ var(--website-typography__font-family-headlines),
287
+ 1.2,
288
+ 700
289
+ );
290
+ font-family: var(--website-typography__font-family-headlines);
291
+ // font-size: 36px !important;
292
+ }
293
+ }
294
+ // .asd20-messaging {
295
+ // padding-bottom: space(2);
296
+ // border-bottom: 10px solid var(--color__tertiary);
297
+ // }
298
+ }
299
+
300
+ @media (min-width: 1024px) {
301
+ .asd20-template-wayfinding-accessibility {
302
+ .asd20-notification-group--floating {
303
+ position: absolute;
304
+ top: space(1);
305
+ // .bell {
306
+ // span {
307
+ // background: var(--color__accent);
308
+ // top: -0.6em !important;
309
+ // right: -0.6em !important;
310
+ // }
311
+ // }
312
+ }
313
+ @include template-desktop;
314
+ margin-left: space(3);
315
+ margin-top: 0;
316
+ .asd20-page-content {
317
+ margin-top: space(0);
318
+ }
319
+ // .intro-message {
320
+ // width: 90%;
321
+ // }
322
+ }
323
+ }
324
+
325
+ @media (min-width: 1790px) {
326
+ .limit-message-width {
327
+ .primary-messaging-section,
328
+ .secondary-messaging-section {
329
+ .asd20-messaging {
330
+ flex: 1 1 var(--minimum-column-width);
331
+ margin: var(--gutter) !important;
332
+ max-width: 30% !important;
333
+ }
334
+ }
335
+ }
336
+ }
337
+ </style>
@@ -80,7 +80,7 @@
80
80
  "id": "4fe4dc65-0d61-497f-8596-e4c5d156bf71",
81
81
  "title": "Supports for Academic Success",
82
82
  "summary": "Academy District 20 provides a host of services so all students have the tools they need to achieve academic success.",
83
- "bodyHtml": "<h2>Overview</h2><p>Each Academy District 20 school has at least one special education teacher and speech/language pathologist. Students may also receive supports from school social workers, school psychologists, school nurses and occupational and physical therapists.&nbsp;</p><p>Our special education staff members work with students and their families in a variety of ways such as: completing assessments, participating in the development of Individual Education Plans (IEPs) and providing specialized support services.&nbsp;</p><p>Special Education staff&nbsp;meet the state standards for licensed teachers and/or special service providers through the Colorado Department of Education.&nbsp;</p><h2>Inclusion at Academy District 20</h2><p>Inclusion is a child-centered, collaborative process that focuses on the unique needs of each student as an equally valued member of the school community. Inclusion gives each student authentic access to the standards driven curriculum and social community through meaningful instruction and interaction supported by the intentional allocation of time and resources.</p><p>This includes the practice of educating all students together to the maximum extent appropriate with the long -range goal of independence, and participation in and contribution to the community.</p>",
83
+ "bodyHtml": "<h2>Overview</h2><p>Each Academy District 20 school has at least one special education teacher and speech/language pathologist. Students may also receive supports from school social workers, school psychologists, school nurses and occupational and physical therapists.&nbsp;</p><p>Our special education staff members work with students and their families in a variety of ways such as: completing assessments, participating in the development of Individual Education Plans (IEPs) and providing specialized support services.&nbsp;</p><p>Special Education staff&nbsp;meet the state standards for licensed teachers and/or special service providers through the Colorado Department of Education.&nbsp;</p><h2>Inclusion at Academy District 20</h2><p>Inclusion is a child-centered, collaborative process that focuses on the unique needs of each student as an equally valued member of the school community. Inclusion gives each student authentic access to the standards driven curriculum and social community through meaningful instruction and interaction supported by the intentional allocation of time and resources.</p><p>This includes the practice of educating all students together to the maximum extent appropriate with the long -range goal of independence, and participation in and contribution to the community.</p><button class='asd20-button' data-trigger-accessibility-modal style='margin: 1rem auto; padding: var(--space-1, 1em); background-color: var(--website-page__background-color, #f5f5f5); color: var(--website-page__title-color, #333); font-family: var(--website-typography__font-family-headlines; font-size: 1rem; font-weight: bold; border: 2px solid var(--color__primary); cursor: pointer; display: flex; justify-content: space-between; align-items: center;'>Notify Us of an Accessibility Issue</button>",
84
84
  "status": "Published",
85
85
  "publishDateTime": "2019-07-02T20:42:12.000Z",
86
86
  "expireDateTime": null,
@@ -0,0 +1,47 @@
1
+ import axios from 'axios'
2
+
3
+ function issueIsValid(issue) {
4
+ return !!issue.firstName && !!issue.lastName && !!issue.email && !!issue.issue
5
+ }
6
+
7
+ export default async function sendAccessibilityIssue(issue, endpoint, apiKey, authCode) {
8
+ if (!issueIsValid(issue)) {
9
+ throw new Error('The issue object is incomplete or invalid.')
10
+ }
11
+
12
+ const payload = {
13
+ name: `${issue.firstName} ${issue.lastName}`,
14
+ email: issue.email,
15
+ phone: issue.phone || '',
16
+ subject: 'Accessibility Issue Reported via External ASD20 Website',
17
+ html: `
18
+ <p><strong>Issue Details:</strong></p>
19
+ <p>${issue.issue}</p>
20
+ <p><strong>Mailing Address:</strong> ${issue.address || 'Not provided'}</p>
21
+ <p><strong>Organization:</strong> ${issue.organization || 'Not provided'}</p>
22
+ `,
23
+ category: Number(process.env.HAPPYFOX_CATEGORY_ID),
24
+ }
25
+
26
+ try {
27
+ const response = await axios.post(endpoint, payload, {
28
+ headers: {
29
+ Authorization: `Basic ${btoa(`${apiKey}:${authCode}`)}`,
30
+ 'Content-Type': 'application/json',
31
+ Accept: 'application/json'
32
+ }
33
+ })
34
+
35
+ return response.data
36
+ } catch (error) {
37
+ if (error.response) {
38
+ throw new Error(
39
+ `HappyFox error ${error.response.status}: ${JSON.stringify(
40
+ error.response.data
41
+ )}`
42
+ )
43
+ } else {
44
+ throw new Error(`Request error: ${error.message}`)
45
+ }
46
+ }
47
+ }