@wral/report-it 0.1.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/README.md +110 -0
- package/dist/define.mjs +18 -0
- package/dist/define.standalone.js +5810 -0
- package/dist/index.mjs +168 -0
- package/dist/wral-code-input-CS9ASu8g.js +1945 -0
- package/package.json +41 -0
|
@@ -0,0 +1,1945 @@
|
|
|
1
|
+
import { css as p, html as l, nothing as c, LitElement as I } from "lit";
|
|
2
|
+
import { getAuthContext as O, createStorage as V, verifyCode as W, extractTokens as S, saveTokens as A, decodeJwtPayload as R, refreshTokenSimple as N, sendCode as H, createUserPool as Y, signUpIfNeeded as K, createCognitoUser as Q, getAuthHeaders as G, STORAGE_KEYS as _, isAuthenticatedSimple as J } from "@wral/sdk-auth-audience";
|
|
3
|
+
const X = p`
|
|
4
|
+
:host {
|
|
5
|
+
display: block;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
font-family: var(--wral-base-font-body, 'IBM Plex Sans', system-ui, sans-serif);
|
|
8
|
+
color: var(--wral-semantic-fg-default, #0F172A);
|
|
9
|
+
line-height: var(--wral-base-leading-normal, 1.5);
|
|
10
|
+
font-size: 16px;
|
|
11
|
+
}
|
|
12
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
13
|
+
.chat {
|
|
14
|
+
max-width: var(--report-it-max-width, 680px);
|
|
15
|
+
margin: 0 auto;
|
|
16
|
+
padding: 24px 16px 32px;
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: 10px;
|
|
20
|
+
}
|
|
21
|
+
.chat > * { scroll-margin-bottom: 84px; }
|
|
22
|
+
.sr-only {
|
|
23
|
+
position: absolute; width: 1px; height: 1px; overflow: hidden;
|
|
24
|
+
clip: rect(0 0 0 0); white-space: nowrap;
|
|
25
|
+
}
|
|
26
|
+
`, Z = p`
|
|
27
|
+
@keyframes pop-in {
|
|
28
|
+
0% { opacity: 0; transform: translateY(16px) scale(0.95); }
|
|
29
|
+
100% { opacity: 1; transform: translateY(0) scale(1); }
|
|
30
|
+
}
|
|
31
|
+
@keyframes chip-in {
|
|
32
|
+
0% { opacity: 0; transform: translateY(10px) scale(0.9); }
|
|
33
|
+
100% { opacity: 1; transform: translateY(0) scale(1); }
|
|
34
|
+
}
|
|
35
|
+
@keyframes dot-bounce {
|
|
36
|
+
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
|
|
37
|
+
30% { transform: translateY(-5px); opacity: 1; }
|
|
38
|
+
}
|
|
39
|
+
.msg, .options, .form-card, .confirm-card, .journey {
|
|
40
|
+
animation: pop-in 340ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
|
41
|
+
}
|
|
42
|
+
.chip { animation: chip-in 300ms cubic-bezier(0.34, 1.56, 0.64, 1) both; }
|
|
43
|
+
.no-anim, .no-anim .chip { animation: none !important; }
|
|
44
|
+
.typing { display: inline-flex; gap: 5px; align-items: center; padding: 16px 18px; }
|
|
45
|
+
.typing span {
|
|
46
|
+
width: 7px; height: 7px; border-radius: 50%;
|
|
47
|
+
background: var(--wral-base-secondary-1, #2594E3);
|
|
48
|
+
animation: dot-bounce 1.1s infinite cubic-bezier(0.3, 0, 0.2, 1);
|
|
49
|
+
}
|
|
50
|
+
.typing span:nth-child(2) { animation-delay: 0.15s; }
|
|
51
|
+
.typing span:nth-child(3) { animation-delay: 0.3s; }
|
|
52
|
+
@media (prefers-reduced-motion: reduce) {
|
|
53
|
+
.msg, .options, .form-card, .confirm-card, .journey, .chip, .typing span {
|
|
54
|
+
animation: none !important;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
`, ee = p`
|
|
58
|
+
.msg { max-width: 85%; padding: 12px 16px; border-radius: 14px; font-size: 15px; }
|
|
59
|
+
.msg.bot {
|
|
60
|
+
background: var(--wral-semantic-surface-default, #FFFFFF);
|
|
61
|
+
border-bottom-left-radius: 4px;
|
|
62
|
+
align-self: flex-start;
|
|
63
|
+
box-shadow: 0 1px 2px rgba(3, 7, 17, 0.07), 0 6px 16px rgba(3, 7, 17, 0.05);
|
|
64
|
+
}
|
|
65
|
+
.msg.user {
|
|
66
|
+
background: var(--wral-base-primary-1, #001D68);
|
|
67
|
+
color: var(--wral-base-gray-10, #FFFFFF);
|
|
68
|
+
border-bottom-right-radius: 4px;
|
|
69
|
+
align-self: flex-end;
|
|
70
|
+
}
|
|
71
|
+
.msg.bot .lead {
|
|
72
|
+
font-family: var(--wral-base-font-ui, 'IBM Plex Sans Condensed', sans-serif);
|
|
73
|
+
font-weight: 700;
|
|
74
|
+
color: var(--wral-base-primary-1, #001D68);
|
|
75
|
+
}
|
|
76
|
+
.msg .sub { display: block; font-size: 13px; color: var(--wral-semantic-fg-muted, #64748B); margin-top: 4px; }
|
|
77
|
+
.msg.user .sub { color: rgba(255, 255, 255, 0.75); }
|
|
78
|
+
.msg.error { border-left: 3px solid var(--wral-base-red-on-light, #D1232A); }
|
|
79
|
+
`, te = p`
|
|
80
|
+
.options { display: flex; flex-wrap: wrap; gap: 8px; align-self: flex-start; max-width: 92%; padding: 2px 0 6px; }
|
|
81
|
+
.chip {
|
|
82
|
+
font-family: var(--wral-base-font-ui, 'IBM Plex Sans Condensed', sans-serif);
|
|
83
|
+
font-size: 14px; font-weight: 600;
|
|
84
|
+
color: var(--wral-base-primary-1, #001D68);
|
|
85
|
+
background: var(--wral-semantic-surface-default, #FFFFFF);
|
|
86
|
+
border: 1.5px solid var(--wral-base-secondary-1, #2594E3);
|
|
87
|
+
border-radius: 999px; padding: 9px 16px; cursor: pointer;
|
|
88
|
+
transition: background 150ms, color 150ms;
|
|
89
|
+
}
|
|
90
|
+
.chip:hover { background: var(--wral-base-secondary-1, #2594E3); color: #FFFFFF; }
|
|
91
|
+
.chip:focus-visible { outline: 2px solid var(--wral-base-secondary-1, #2594E3); outline-offset: 2px; }
|
|
92
|
+
.chip:active { transform: scale(0.97); }
|
|
93
|
+
`, re = p`
|
|
94
|
+
.form-card {
|
|
95
|
+
align-self: stretch;
|
|
96
|
+
background: var(--wral-semantic-surface-default, #FFFFFF);
|
|
97
|
+
border: 1px solid var(--wral-semantic-border-muted, #E2E8F0);
|
|
98
|
+
border-radius: 12px; padding: 18px 20px;
|
|
99
|
+
box-shadow: 0 1px 2px rgba(3, 7, 17, 0.07), 0 6px 16px rgba(3, 7, 17, 0.05);
|
|
100
|
+
}
|
|
101
|
+
.form-card .field { margin-bottom: 14px; }
|
|
102
|
+
.form-card label {
|
|
103
|
+
display: block;
|
|
104
|
+
font-family: var(--wral-base-font-ui, 'IBM Plex Sans Condensed', sans-serif);
|
|
105
|
+
font-size: 13px; font-weight: 700; text-transform: uppercase;
|
|
106
|
+
letter-spacing: 0.04em;
|
|
107
|
+
color: var(--wral-base-primary-1, #001D68);
|
|
108
|
+
margin-bottom: 5px;
|
|
109
|
+
}
|
|
110
|
+
.form-card label .opt {
|
|
111
|
+
font-weight: 400; text-transform: none; letter-spacing: 0;
|
|
112
|
+
color: var(--wral-semantic-fg-subtle, #64748B);
|
|
113
|
+
}
|
|
114
|
+
.form-card input[type=text], .form-card input[type=url], .form-card input[type=email],
|
|
115
|
+
.form-card input[type=datetime-local], .form-card select, .form-card textarea {
|
|
116
|
+
width: 100%; font-family: inherit; font-size: 15px; color: inherit;
|
|
117
|
+
border: 1px solid var(--wral-semantic-border-default, #CBD5E1);
|
|
118
|
+
border-radius: 8px; padding: 10px 12px;
|
|
119
|
+
background: var(--wral-semantic-surface-default, #FFFFFF);
|
|
120
|
+
}
|
|
121
|
+
.form-card textarea { min-height: 84px; resize: vertical; }
|
|
122
|
+
.form-card input:focus-visible, .form-card select:focus-visible, .form-card textarea:focus-visible {
|
|
123
|
+
outline: 2px solid var(--wral-base-secondary-1, #2594E3); outline-offset: 1px;
|
|
124
|
+
}
|
|
125
|
+
.consent-row {
|
|
126
|
+
display: flex; gap: 10px; align-items: flex-start;
|
|
127
|
+
font-size: 13.5px; color: var(--wral-semantic-fg-muted, #475569);
|
|
128
|
+
background: var(--wral-semantic-surface-sunken, #F1F5F9);
|
|
129
|
+
border-radius: 8px; padding: 12px 14px;
|
|
130
|
+
}
|
|
131
|
+
.consent-row input { margin-top: 3px; }
|
|
132
|
+
.file-note { font-size: 13px; color: var(--wral-semantic-fg-subtle, #64748B); margin-top: 4px; }
|
|
133
|
+
.err { color: var(--wral-base-red-on-light, #D1232A); font-size: 13px; margin: 4px 0 10px; }
|
|
134
|
+
.btn-primary {
|
|
135
|
+
font-family: var(--wral-base-font-ui, 'IBM Plex Sans Condensed', sans-serif);
|
|
136
|
+
font-size: 15px; font-weight: 700; color: #FFFFFF;
|
|
137
|
+
background: var(--wral-base-primary-1, #001D68);
|
|
138
|
+
border: none; border-radius: 8px; padding: 11px 22px; cursor: pointer;
|
|
139
|
+
}
|
|
140
|
+
.btn-primary:hover { background: var(--wral-base-primary-2, #16307E); }
|
|
141
|
+
.btn-primary:focus-visible { outline: 2px solid var(--wral-base-secondary-1, #2594E3); outline-offset: 2px; }
|
|
142
|
+
.btn-primary:active { transform: scale(0.97); }
|
|
143
|
+
.btn-primary:disabled { opacity: var(--wral-semantic-opacity-disabled, 0.6); cursor: default; }
|
|
144
|
+
.btn-ghost {
|
|
145
|
+
font-family: var(--wral-base-font-ui, 'IBM Plex Sans Condensed', sans-serif);
|
|
146
|
+
font-size: 14px; font-weight: 700;
|
|
147
|
+
color: var(--wral-base-primary-1, #001D68);
|
|
148
|
+
background: var(--wral-semantic-surface-default, #FFFFFF);
|
|
149
|
+
border: 1.5px solid var(--wral-semantic-border-default, #CBD5E1);
|
|
150
|
+
border-radius: var(--wral-base-radius-md, 8px);
|
|
151
|
+
padding: 9px 18px; cursor: pointer;
|
|
152
|
+
box-shadow: 0 1px 2px rgba(3, 7, 17, 0.05);
|
|
153
|
+
transition: border-color 150ms, background 150ms;
|
|
154
|
+
}
|
|
155
|
+
.btn-ghost:hover { border-color: var(--wral-base-secondary-1, #2594E3); }
|
|
156
|
+
.btn-ghost:active { transform: scale(0.97); }
|
|
157
|
+
.btn-ghost:focus-visible { outline: 2px solid var(--wral-base-secondary-1, #2594E3); outline-offset: 2px; }
|
|
158
|
+
`, se = p`
|
|
159
|
+
.login-card { padding: 28px 26px; }
|
|
160
|
+
.login-card h2 {
|
|
161
|
+
font-family: var(--wral-base-font-display, 'IBM Plex Sans Condensed', sans-serif);
|
|
162
|
+
font-size: 26px; font-weight: 700;
|
|
163
|
+
color: var(--color-primary-dark, #00144A);
|
|
164
|
+
margin: 0 0 8px;
|
|
165
|
+
}
|
|
166
|
+
.login-card .login-sub { font-size: 14.5px; color: var(--color-gray-5, #6C6C6C); margin: 0 0 20px; line-height: 1.5; }
|
|
167
|
+
.login-card .login-sub strong { color: var(--color-gray-8, #263238); }
|
|
168
|
+
.login-btn {
|
|
169
|
+
width: 100%; margin-top: 6px;
|
|
170
|
+
font-family: var(--wral-base-font-ui, 'IBM Plex Sans Condensed', sans-serif);
|
|
171
|
+
font-size: 16px; font-weight: 700; color: #FFFFFF;
|
|
172
|
+
border: none; border-radius: 2px; padding: 15px 20px; cursor: pointer;
|
|
173
|
+
background: linear-gradient(180deg, var(--color-secondary, #2594E3), var(--color-secondary-dark, #1D76B5));
|
|
174
|
+
transition: opacity 150ms, filter 150ms;
|
|
175
|
+
}
|
|
176
|
+
.login-btn:disabled { background: linear-gradient(180deg, #A5C6E8, #8FB4DA); cursor: default; }
|
|
177
|
+
.login-btn:not(:disabled):hover { filter: brightness(1.06); }
|
|
178
|
+
.login-btn:focus-visible { outline: 2px solid var(--color-primary, #001D68); outline-offset: 2px; }
|
|
179
|
+
.change-email {
|
|
180
|
+
display: block; width: 100%; text-align: left;
|
|
181
|
+
font-family: var(--wral-base-font-body, 'IBM Plex Sans', sans-serif);
|
|
182
|
+
font-size: 16px; font-weight: 600;
|
|
183
|
+
color: var(--color-primary-dark, #00144A);
|
|
184
|
+
background: #FFFFFF;
|
|
185
|
+
border: 1px solid var(--color-gray-3, #D7D4D2);
|
|
186
|
+
border-radius: 2px; padding: 16px 18px; margin-bottom: 18px; cursor: pointer;
|
|
187
|
+
}
|
|
188
|
+
.change-email:hover { border-color: var(--color-gray-4, #7C7C7C); }
|
|
189
|
+
.login-help { text-align: center; font-size: 14.5px; color: var(--color-gray-5, #6C6C6C); margin: 18px 0 0; }
|
|
190
|
+
.login-help a, .login-help button {
|
|
191
|
+
color: var(--color-secondary, #2594E3); font-weight: 600; cursor: pointer;
|
|
192
|
+
background: none; border: none; font-size: inherit; font-family: inherit; padding: 0;
|
|
193
|
+
text-decoration: underline;
|
|
194
|
+
}
|
|
195
|
+
.login-why { border-top: 1px solid var(--color-gray-2, #E7E5E4); margin-top: 22px; padding-top: 16px; }
|
|
196
|
+
.login-why h3 {
|
|
197
|
+
font-family: var(--wral-base-font-ui, 'IBM Plex Sans Condensed', sans-serif);
|
|
198
|
+
font-size: 15px; font-weight: 700;
|
|
199
|
+
color: var(--color-primary-dark, #00144A); margin: 0 0 8px;
|
|
200
|
+
}
|
|
201
|
+
.login-why ul { margin: 0; padding-left: 20px; font-size: 14px; color: var(--color-gray-6, #565454); }
|
|
202
|
+
.login-why li { margin-bottom: 4px; line-height: 1.5; }
|
|
203
|
+
`, ie = p`
|
|
204
|
+
.review-list { margin: 0; padding: 0; list-style: none; font-size: 14px; }
|
|
205
|
+
.review-list li {
|
|
206
|
+
display: grid; grid-template-columns: 130px 1fr; gap: 10px;
|
|
207
|
+
padding: 7px 0; border-bottom: 1px solid var(--wral-semantic-border-muted, #E2E8F0);
|
|
208
|
+
}
|
|
209
|
+
.review-list li:last-child { border-bottom: none; }
|
|
210
|
+
.review-list .k {
|
|
211
|
+
font-family: var(--wral-base-font-mono, 'IBM Plex Mono', monospace);
|
|
212
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
|
|
213
|
+
color: var(--wral-semantic-fg-subtle, #64748B); padding-top: 2px;
|
|
214
|
+
}
|
|
215
|
+
.flag-pill {
|
|
216
|
+
display: inline-block;
|
|
217
|
+
font-family: var(--wral-base-font-ui, 'IBM Plex Sans Condensed', sans-serif);
|
|
218
|
+
font-size: 11px; font-weight: 700; text-transform: uppercase;
|
|
219
|
+
letter-spacing: 0.05em; padding: 2px 9px;
|
|
220
|
+
border-radius: var(--wral-base-radius-md, 8px);
|
|
221
|
+
}
|
|
222
|
+
.flag-pill.urgent { background: #F6D5D6; color: var(--wral-base-red-deep, #951C21); }
|
|
223
|
+
.flag-pill.moderate { background: var(--wral-base-yellow-1, #FFEC19); color: #000000; }
|
|
224
|
+
.event-chip {
|
|
225
|
+
display: inline-block;
|
|
226
|
+
font-family: var(--wral-base-font-mono, 'IBM Plex Mono', monospace);
|
|
227
|
+
font-size: 11.5px;
|
|
228
|
+
background: var(--wral-semantic-surface-sunken, #F1F5F9);
|
|
229
|
+
border: 1px solid var(--wral-semantic-border-default, #CBD5E1);
|
|
230
|
+
border-radius: var(--wral-base-radius-md, 8px);
|
|
231
|
+
padding: 2px 8px;
|
|
232
|
+
color: var(--wral-base-primary-1, #001D68);
|
|
233
|
+
}
|
|
234
|
+
@media (max-width: 540px) { .review-list li { grid-template-columns: 1fr; gap: 2px; } }
|
|
235
|
+
`, ae = p`
|
|
236
|
+
.upload-row { display: flex; align-items: center; gap: 10px; padding: 7px 0; font-size: 13.5px; }
|
|
237
|
+
.upload-row .name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
238
|
+
.upload-row .state { font-size: 12px; color: var(--wral-semantic-fg-subtle, #64748B); }
|
|
239
|
+
.upload-row .state.failed { color: var(--wral-base-red-on-light, #D1232A); font-weight: 600; }
|
|
240
|
+
.upload-bar { flex: 1; height: 6px; border-radius: 3px; background: var(--wral-semantic-surface-sunken, #F1F5F9); overflow: hidden; }
|
|
241
|
+
.upload-bar .fill { height: 100%; background: var(--wral-base-secondary-1, #2594E3); transition: width 200ms; }
|
|
242
|
+
.retry-chip {
|
|
243
|
+
font-family: var(--wral-base-font-ui, 'IBM Plex Sans Condensed', sans-serif);
|
|
244
|
+
font-size: 12px; font-weight: 700;
|
|
245
|
+
color: var(--wral-base-primary-1, #001D68);
|
|
246
|
+
background: #FFFFFF;
|
|
247
|
+
border: 1.5px solid var(--wral-base-secondary-1, #2594E3);
|
|
248
|
+
border-radius: 999px; padding: 3px 12px; cursor: pointer;
|
|
249
|
+
}
|
|
250
|
+
.retry-chip:focus-visible { outline: 2px solid var(--wral-base-secondary-1, #2594E3); outline-offset: 2px; }
|
|
251
|
+
`, ne = p`
|
|
252
|
+
.confirm-card {
|
|
253
|
+
align-self: stretch;
|
|
254
|
+
background: var(--wral-base-primary-1, #001D68);
|
|
255
|
+
color: #FFFFFF; border-radius: 12px; padding: 22px 24px;
|
|
256
|
+
box-shadow: 0 2px 6px rgba(3, 7, 17, 0.18), 0 12px 28px rgba(3, 7, 17, 0.14);
|
|
257
|
+
}
|
|
258
|
+
.confirm-card .c-head { display: flex; gap: 14px; align-items: center; margin-bottom: 12px; }
|
|
259
|
+
.c-check {
|
|
260
|
+
flex-shrink: 0; width: 42px; height: 42px; border-radius: 50%;
|
|
261
|
+
background: var(--wral-base-green-1, #72B509);
|
|
262
|
+
display: flex; align-items: center; justify-content: center;
|
|
263
|
+
box-shadow: 0 0 0 5px rgba(114, 181, 9, 0.25);
|
|
264
|
+
}
|
|
265
|
+
.confirm-card .eyebrow {
|
|
266
|
+
font-family: var(--wral-base-font-ui, 'IBM Plex Sans Condensed', sans-serif);
|
|
267
|
+
font-size: 12px; font-weight: 700; text-transform: uppercase;
|
|
268
|
+
letter-spacing: 0.08em;
|
|
269
|
+
color: var(--wral-base-secondary-2, #50A9E8);
|
|
270
|
+
margin-bottom: 2px;
|
|
271
|
+
}
|
|
272
|
+
.confirm-card h2 {
|
|
273
|
+
font-family: var(--wral-base-font-display, 'IBM Plex Sans Condensed', sans-serif);
|
|
274
|
+
font-size: 22px; margin: 0; color: #FFFFFF; line-height: 1.25;
|
|
275
|
+
}
|
|
276
|
+
.confirm-card p { margin: 0 0 8px; font-size: 14.5px; color: rgba(255, 255, 255, 0.88); }
|
|
277
|
+
.confirm-card .refs { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 16px; }
|
|
278
|
+
.confirm-card .ref {
|
|
279
|
+
font-family: var(--wral-base-font-mono, 'IBM Plex Mono', monospace);
|
|
280
|
+
font-size: 13px;
|
|
281
|
+
background: rgba(255, 255, 255, 0.12);
|
|
282
|
+
border: 1px solid rgba(255, 255, 255, 0.18);
|
|
283
|
+
border-radius: var(--wral-base-radius-md, 8px);
|
|
284
|
+
padding: 6px 10px; display: inline-block; color: #FFFFFF;
|
|
285
|
+
}
|
|
286
|
+
.confirm-card .ref .rk {
|
|
287
|
+
display: block; font-size: 9px; text-transform: uppercase;
|
|
288
|
+
letter-spacing: 0.1em; color: rgba(255, 255, 255, 0.6); margin-bottom: 1px;
|
|
289
|
+
}
|
|
290
|
+
.c-again {
|
|
291
|
+
font-family: var(--wral-base-font-ui, 'IBM Plex Sans Condensed', sans-serif);
|
|
292
|
+
font-size: 14px; font-weight: 700; color: #FFFFFF;
|
|
293
|
+
background: transparent;
|
|
294
|
+
border: 1.5px solid rgba(255, 255, 255, 0.45);
|
|
295
|
+
border-radius: var(--wral-base-radius-md, 8px);
|
|
296
|
+
padding: 9px 18px; cursor: pointer; transition: background 150ms;
|
|
297
|
+
}
|
|
298
|
+
.c-again:hover { background: rgba(255, 255, 255, 0.12); }
|
|
299
|
+
.c-again:focus-visible { outline: 2px solid var(--wral-base-secondary-2, #50A9E8); outline-offset: 2px; }
|
|
300
|
+
.c-again:active { transform: scale(0.97); }
|
|
301
|
+
`, oe = p`
|
|
302
|
+
.journey {
|
|
303
|
+
align-self: stretch;
|
|
304
|
+
background: var(--wral-semantic-surface-default, #FFFFFF);
|
|
305
|
+
border: 1px solid var(--wral-semantic-border-muted, #E2E8F0);
|
|
306
|
+
border-radius: 12px; padding: 20px 22px;
|
|
307
|
+
box-shadow: 0 1px 2px rgba(3, 7, 17, 0.07), 0 6px 16px rgba(3, 7, 17, 0.05);
|
|
308
|
+
}
|
|
309
|
+
.j-eyebrow {
|
|
310
|
+
font-family: var(--wral-base-font-ui, 'IBM Plex Sans Condensed', sans-serif);
|
|
311
|
+
font-size: 12px; font-weight: 700; text-transform: uppercase;
|
|
312
|
+
letter-spacing: 0.08em;
|
|
313
|
+
color: var(--wral-base-red-on-light, #D1232A);
|
|
314
|
+
margin-bottom: 2px;
|
|
315
|
+
}
|
|
316
|
+
.j-title {
|
|
317
|
+
font-family: var(--wral-base-font-display, 'IBM Plex Sans Condensed', sans-serif);
|
|
318
|
+
font-size: 18px; font-weight: 700;
|
|
319
|
+
color: var(--wral-base-primary-1, #001D68); margin: 0 0 10px;
|
|
320
|
+
}
|
|
321
|
+
.j-step { position: relative; display: flex; gap: 12px; padding: 9px 0; }
|
|
322
|
+
.j-step:not(:last-child)::before {
|
|
323
|
+
content: ''; position: absolute; left: 12px; top: 38px; bottom: -6px;
|
|
324
|
+
width: 1.5px; background: var(--wral-semantic-border-default, #CBD5E1);
|
|
325
|
+
}
|
|
326
|
+
.j-dot {
|
|
327
|
+
flex-shrink: 0; width: 26px; height: 26px; border-radius: 50%;
|
|
328
|
+
background: var(--wral-semantic-surface-sunken, #F1F5F9);
|
|
329
|
+
border: 1.5px solid var(--wral-base-secondary-1, #2594E3);
|
|
330
|
+
color: var(--wral-base-primary-1, #001D68);
|
|
331
|
+
font-family: var(--wral-base-font-mono, 'IBM Plex Mono', monospace);
|
|
332
|
+
font-size: 11px;
|
|
333
|
+
display: flex; align-items: center; justify-content: center;
|
|
334
|
+
position: relative; z-index: 1;
|
|
335
|
+
}
|
|
336
|
+
.j-body .j-t {
|
|
337
|
+
font-family: var(--wral-base-font-ui, 'IBM Plex Sans Condensed', sans-serif);
|
|
338
|
+
font-size: 14.5px; font-weight: 700;
|
|
339
|
+
color: var(--wral-base-primary-1, #001D68); margin-bottom: 2px;
|
|
340
|
+
}
|
|
341
|
+
.j-body .j-d { font-size: 13.5px; color: var(--wral-semantic-fg-muted, #475569); line-height: 1.55; }
|
|
342
|
+
`, le = p`
|
|
343
|
+
.bottom-bar {
|
|
344
|
+
position: sticky; bottom: 0; z-index: 20;
|
|
345
|
+
background: var(--wral-semantic-surface-default, #FFFFFF);
|
|
346
|
+
border-top: 1px solid var(--wral-semantic-border-default, #CBD5E1);
|
|
347
|
+
}
|
|
348
|
+
.bottom-bar .inner {
|
|
349
|
+
max-width: var(--report-it-max-width, 680px);
|
|
350
|
+
margin: 0 auto; padding: 10px 16px;
|
|
351
|
+
display: flex; align-items: center; gap: 8px;
|
|
352
|
+
}
|
|
353
|
+
.bottom-bar .note { margin-left: auto; font-size: 12px; color: var(--wral-semantic-fg-subtle, #64748B); }
|
|
354
|
+
@media (max-width: 540px) { .bottom-bar .note { display: none; } }
|
|
355
|
+
`, de = [X, Z, ee, te, re, se, ie, ae, ne, oe, le];
|
|
356
|
+
function h(r, e, { noAnim: t = !1, error: s = !1 } = {}) {
|
|
357
|
+
return l`
|
|
358
|
+
<div class="msg bot ${t ? "no-anim" : ""} ${s ? "error" : ""}">
|
|
359
|
+
<span class="lead">${r}</span>
|
|
360
|
+
${e ? l`<span class="sub">${e}</span>` : c}
|
|
361
|
+
</div>
|
|
362
|
+
`;
|
|
363
|
+
}
|
|
364
|
+
function v(r, { noAnim: e = !1 } = {}) {
|
|
365
|
+
return l`<div class="msg user ${e ? "no-anim" : ""}">${r}</div>`;
|
|
366
|
+
}
|
|
367
|
+
function $() {
|
|
368
|
+
return l`<div class="msg bot typing"><span></span><span></span><span></span></div>`;
|
|
369
|
+
}
|
|
370
|
+
function ce(r) {
|
|
371
|
+
return r ? l`<span class="flag-pill ${r}">${r === "urgent" ? "Urgent" : "Moderate"}</span>` : c;
|
|
372
|
+
}
|
|
373
|
+
function ue() {
|
|
374
|
+
return l`
|
|
375
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="3"
|
|
376
|
+
stroke-linecap="round" stroke-linejoin="round" width="20" height="20"
|
|
377
|
+
aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg>
|
|
378
|
+
`;
|
|
379
|
+
}
|
|
380
|
+
class w extends Error {
|
|
381
|
+
constructor(e, t) {
|
|
382
|
+
super(`api-forms request failed with status ${e}`), this.status = e, this.body = t;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function pe({
|
|
386
|
+
baseUrl: r,
|
|
387
|
+
fetchFn: e = (...i) => fetch(...i),
|
|
388
|
+
xhrFactory: t = () => new XMLHttpRequest(),
|
|
389
|
+
getAuthHeaders: s
|
|
390
|
+
}) {
|
|
391
|
+
const i = String(r || "").replace(/\/+$/, "");
|
|
392
|
+
async function a(n, { method: o = "GET", body: d, auth: f = !1 } = {}) {
|
|
393
|
+
const u = { Accept: "application/json" };
|
|
394
|
+
f && Object.assign(u, await s()), d !== void 0 && (u["Content-Type"] = "application/json");
|
|
395
|
+
let m;
|
|
396
|
+
try {
|
|
397
|
+
m = await e(`${i}${n}`, {
|
|
398
|
+
method: o,
|
|
399
|
+
headers: u,
|
|
400
|
+
...d !== void 0 ? { body: JSON.stringify(d) } : {}
|
|
401
|
+
});
|
|
402
|
+
} catch (y) {
|
|
403
|
+
throw Object.assign(new w(0, null), { cause: y });
|
|
404
|
+
}
|
|
405
|
+
let b = null;
|
|
406
|
+
try {
|
|
407
|
+
b = await m.json();
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
if (!m.ok)
|
|
411
|
+
throw new w(m.status, b);
|
|
412
|
+
return b;
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
/** @returns {Promise<Array>} published form documents */
|
|
416
|
+
listForms: async () => {
|
|
417
|
+
const { forms: n } = await a("/v1/forms");
|
|
418
|
+
return (n || []).filter((o) => o?.formId && o?.start && Array.isArray(o?.nodes));
|
|
419
|
+
},
|
|
420
|
+
/** @returns {Promise<Array<{assetId,url,method,expires,assetUrl}>>} */
|
|
421
|
+
createUploads: async (n) => {
|
|
422
|
+
const { uploads: o } = await a("/v1/uploads", { method: "POST", body: n, auth: !0 });
|
|
423
|
+
return o || [];
|
|
424
|
+
},
|
|
425
|
+
/**
|
|
426
|
+
* PUT one file to its presigned URL. XHR (not fetch) so upload
|
|
427
|
+
* progress is observable. Resolves on 2xx; rejects with
|
|
428
|
+
* ApiError(status) otherwise. Returns { promise, abort }.
|
|
429
|
+
*/
|
|
430
|
+
uploadFile: ({ url: n, file: o, contentType: d, onProgress: f = () => {
|
|
431
|
+
} }) => {
|
|
432
|
+
const u = t();
|
|
433
|
+
return { promise: new Promise((b, y) => {
|
|
434
|
+
u.open("PUT", n), u.setRequestHeader("Content-Type", d || o.type), u.upload && (u.upload.onprogress = (C) => {
|
|
435
|
+
f({ loaded: C.loaded, total: C.total || o.size });
|
|
436
|
+
}), u.onload = () => {
|
|
437
|
+
u.status >= 200 && u.status < 300 ? b({ status: u.status }) : y(new w(u.status, null));
|
|
438
|
+
}, u.onerror = () => y(new w(0, null)), u.onabort = () => y(new w(0, { aborted: !0 })), u.send(o);
|
|
439
|
+
}), abort: () => u.abort() };
|
|
440
|
+
},
|
|
441
|
+
/** @returns {Promise<{requestId,reference,confirmation,routing}>} */
|
|
442
|
+
putRequest: async (n, o) => await a(`/v1/requests/${n}`, { method: "PUT", body: o, auth: !0 })
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function he() {
|
|
446
|
+
const { config: r, storage: e } = O();
|
|
447
|
+
let t = null;
|
|
448
|
+
return {
|
|
449
|
+
isAuthenticated: () => J(),
|
|
450
|
+
signedInEmail: () => e.get(_.userEmail) || null,
|
|
451
|
+
getAuthHeaders: () => G(r, e),
|
|
452
|
+
/**
|
|
453
|
+
* Email step. Keeps the challenge user in memory for verifyCode.
|
|
454
|
+
* @returns {{challengeRequired?: boolean, authenticated?: boolean}}
|
|
455
|
+
*/
|
|
456
|
+
sendCode: async (s) => {
|
|
457
|
+
const i = await H(
|
|
458
|
+
r,
|
|
459
|
+
s,
|
|
460
|
+
e,
|
|
461
|
+
Y,
|
|
462
|
+
K,
|
|
463
|
+
Q,
|
|
464
|
+
S,
|
|
465
|
+
A,
|
|
466
|
+
R
|
|
467
|
+
);
|
|
468
|
+
return t = i.cognitoUser || null, {
|
|
469
|
+
challengeRequired: !!i.challengeRequired,
|
|
470
|
+
authenticated: !!i.authenticated
|
|
471
|
+
};
|
|
472
|
+
},
|
|
473
|
+
/**
|
|
474
|
+
* Code step; refreshes immediately after verify so the session
|
|
475
|
+
* starts with a full lifetime (house pattern).
|
|
476
|
+
*/
|
|
477
|
+
verifyCode: async (s) => {
|
|
478
|
+
if (!t)
|
|
479
|
+
throw new Error("Please enter your email again to get a new code.");
|
|
480
|
+
try {
|
|
481
|
+
await W(
|
|
482
|
+
t,
|
|
483
|
+
s,
|
|
484
|
+
e,
|
|
485
|
+
S,
|
|
486
|
+
A,
|
|
487
|
+
R
|
|
488
|
+
);
|
|
489
|
+
} catch {
|
|
490
|
+
throw new Error("Invalid or expired code. Please request a new one and try again.");
|
|
491
|
+
}
|
|
492
|
+
return await N(), { success: !0 };
|
|
493
|
+
},
|
|
494
|
+
/** Local sign-out only ("use a different email"). */
|
|
495
|
+
signOut: () => {
|
|
496
|
+
t = null, V().clear();
|
|
497
|
+
},
|
|
498
|
+
/**
|
|
499
|
+
* Detect a login completed in ANOTHER tab (the storage event
|
|
500
|
+
* never fires in the tab that wrote). Returns an unsubscribe.
|
|
501
|
+
*/
|
|
502
|
+
onCrossTabLogin: (s) => {
|
|
503
|
+
const i = (a) => {
|
|
504
|
+
(a.key === _.accessToken || a.key === _.idToken) && s();
|
|
505
|
+
};
|
|
506
|
+
return window.addEventListener("storage", i), () => window.removeEventListener("storage", i);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function fe({ baseUrl: r }) {
|
|
511
|
+
const e = he();
|
|
512
|
+
return { ...pe({ baseUrl: r, getAuthHeaders: e.getAuthHeaders }), ...e };
|
|
513
|
+
}
|
|
514
|
+
function g(r, e) {
|
|
515
|
+
return (r?.nodes || []).find((t) => t.id === e);
|
|
516
|
+
}
|
|
517
|
+
function M(r, e = []) {
|
|
518
|
+
const t = [];
|
|
519
|
+
let s = r?.start;
|
|
520
|
+
for (const a of e) {
|
|
521
|
+
const n = g(r, s);
|
|
522
|
+
if (!n)
|
|
523
|
+
return { nodeId: null, node: null, flags: t, error: { step: a, reason: `unknown node: ${s}` } };
|
|
524
|
+
if (n.type === "terminal" || n.type === "redirect")
|
|
525
|
+
return { nodeId: null, node: null, flags: t, error: { step: a, reason: `steps continue past ${n.type}` } };
|
|
526
|
+
if (a.nodeId !== s || a.kind !== n.type)
|
|
527
|
+
return { nodeId: null, node: null, flags: t, error: { step: a, reason: `step does not match node ${s}` } };
|
|
528
|
+
if (n.type === "choice") {
|
|
529
|
+
const o = (n.options || []).find((d) => d.id === a.optionId);
|
|
530
|
+
if (!o)
|
|
531
|
+
return { nodeId: null, node: null, flags: t, error: { step: a, reason: `unknown option: ${a.optionId}` } };
|
|
532
|
+
t.push(...o.flags || []), s = o.next;
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
s = n.next;
|
|
536
|
+
}
|
|
537
|
+
const i = g(r, s);
|
|
538
|
+
return i ? { nodeId: s, node: i, flags: j(t), error: null } : { nodeId: null, node: null, flags: t, error: { step: null, reason: `unknown node: ${s}` } };
|
|
539
|
+
}
|
|
540
|
+
function ge(r, e = []) {
|
|
541
|
+
const t = [];
|
|
542
|
+
for (const s of e) {
|
|
543
|
+
if (s.kind !== "collect") continue;
|
|
544
|
+
const i = g(r, s.nodeId);
|
|
545
|
+
t.push(...i?.fields || []);
|
|
546
|
+
}
|
|
547
|
+
return t;
|
|
548
|
+
}
|
|
549
|
+
function me(r = []) {
|
|
550
|
+
const e = {};
|
|
551
|
+
for (const t of r)
|
|
552
|
+
t.kind === "collect" && Object.assign(e, t.values || {});
|
|
553
|
+
return e;
|
|
554
|
+
}
|
|
555
|
+
function be(r = []) {
|
|
556
|
+
return r.filter((e) => e.kind === "choice").map((e) => `${e.nodeId}:${e.optionId}`);
|
|
557
|
+
}
|
|
558
|
+
function U(r, e = []) {
|
|
559
|
+
const t = [];
|
|
560
|
+
for (const s of e) {
|
|
561
|
+
if (s.kind !== "choice") continue;
|
|
562
|
+
const a = (g(r, s.nodeId)?.options || []).find((n) => n.id === s.optionId);
|
|
563
|
+
t.push(...a?.flags || []);
|
|
564
|
+
}
|
|
565
|
+
return j(t);
|
|
566
|
+
}
|
|
567
|
+
function ye(r = []) {
|
|
568
|
+
return r.includes("urgent") ? "urgent" : r.includes("moderate") ? "moderate" : null;
|
|
569
|
+
}
|
|
570
|
+
function j(r) {
|
|
571
|
+
return [...new Set(r)];
|
|
572
|
+
}
|
|
573
|
+
const we = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/, D = {
|
|
574
|
+
text: 500,
|
|
575
|
+
textarea: 5e3,
|
|
576
|
+
url: 2048
|
|
577
|
+
};
|
|
578
|
+
function xe(r, e) {
|
|
579
|
+
if (e == null || e === "" || r.kind === "location" && ve(e))
|
|
580
|
+
return r.required ? `Please fill in: ${r.label.toLowerCase()}.` : null;
|
|
581
|
+
switch (r.kind) {
|
|
582
|
+
case "text":
|
|
583
|
+
case "textarea": {
|
|
584
|
+
const s = r.maxLength || D[r.kind];
|
|
585
|
+
return typeof e != "string" ? `Please enter text for ${r.label.toLowerCase()}.` : e.length > s ? `Please keep ${r.label.toLowerCase()} under ${s} characters.` : null;
|
|
586
|
+
}
|
|
587
|
+
case "url": {
|
|
588
|
+
if (typeof e != "string" || e.length > D.url)
|
|
589
|
+
return "Please enter a valid link.";
|
|
590
|
+
try {
|
|
591
|
+
return new URL(e), null;
|
|
592
|
+
} catch {
|
|
593
|
+
return "Please enter a valid link, starting with https://";
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
case "datetime":
|
|
597
|
+
return we.test(String(e)) ? null : "Please pick a date and time.";
|
|
598
|
+
case "select":
|
|
599
|
+
return (r.options || []).map((i) => i.id).includes(e) ? null : `Please choose one of the options for ${r.label.toLowerCase()}.`;
|
|
600
|
+
case "consent":
|
|
601
|
+
return e === !0 ? null : "Please confirm the rights statement so we can use what you send.";
|
|
602
|
+
case "location":
|
|
603
|
+
return ke(r, e);
|
|
604
|
+
case "media":
|
|
605
|
+
return null;
|
|
606
|
+
default:
|
|
607
|
+
return `Unsupported field: ${r.kind}`;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function ve(r) {
|
|
611
|
+
return r == null ? !0 : typeof r != "object" ? !1 : !r.county && !r.where && !(Array.isArray(r.gps) && r.gps.length);
|
|
612
|
+
}
|
|
613
|
+
function ke(r, e) {
|
|
614
|
+
return typeof e != "object" || Array.isArray(e) ? "Please tell us where this was." : e.county !== void 0 && (typeof e.county != "string" || e.county.length > 100) ? "Please choose a county." : e.where !== void 0 && (typeof e.where != "string" || e.where.length > 500) ? "Please shorten the location description." : e.gps !== void 0 && !(Array.isArray(e.gps) && e.gps.length === 2 && e.gps.every((s) => typeof s == "number" && Number.isFinite(s))) ? "Location coordinates look wrong; try re-sharing your location." : r.required && !e.county && !e.where ? `Please fill in: ${r.label.toLowerCase()}.` : null;
|
|
615
|
+
}
|
|
616
|
+
function T(r, e = {}) {
|
|
617
|
+
const t = {};
|
|
618
|
+
for (const s of r) {
|
|
619
|
+
if (s.kind === "media") continue;
|
|
620
|
+
const i = xe(s, e[s.key]);
|
|
621
|
+
i && (t[s.key] = i);
|
|
622
|
+
}
|
|
623
|
+
return { ok: Object.keys(t).length === 0, errors: t };
|
|
624
|
+
}
|
|
625
|
+
function F(r = [], e = {}, { required: t = !1 } = {}) {
|
|
626
|
+
const {
|
|
627
|
+
maxFiles: s = 5,
|
|
628
|
+
maxFileBytes: i = 512 * 1024 * 1024,
|
|
629
|
+
allowedTypes: a = []
|
|
630
|
+
} = e, n = [];
|
|
631
|
+
return t && r.length === 0 && n.push({ reason: "required", message: "Please attach at least one photo or video." }), r.length > s && n.push({
|
|
632
|
+
reason: "count",
|
|
633
|
+
message: `This form accepts at most ${s} ${s === 1 ? "file" : "files"}.`
|
|
634
|
+
}), r.forEach((o, d) => {
|
|
635
|
+
a.length && !a.includes(o.type) && n.push({
|
|
636
|
+
index: d,
|
|
637
|
+
reason: "type",
|
|
638
|
+
message: `${o.name || "A file"} is a type this form does not accept.`
|
|
639
|
+
}), o.size > i && n.push({
|
|
640
|
+
index: d,
|
|
641
|
+
reason: "size",
|
|
642
|
+
message: `${o.name || "A file"} is larger than this form allows.`
|
|
643
|
+
});
|
|
644
|
+
}), { ok: n.length === 0, errors: n };
|
|
645
|
+
}
|
|
646
|
+
function z({ formId: r, requestId: e, files: t }) {
|
|
647
|
+
return {
|
|
648
|
+
formId: r,
|
|
649
|
+
requestId: e,
|
|
650
|
+
files: t.map((s) => ({ contentType: s.type, size: s.size }))
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function _e({ form: r, steps: e, media: t = [] }) {
|
|
654
|
+
const s = ge(r, e), i = me(e), a = {};
|
|
655
|
+
for (const n of s) {
|
|
656
|
+
if (n.kind === "media") continue;
|
|
657
|
+
const o = i[n.key];
|
|
658
|
+
if (!(o == null || o === "") && !(n.kind === "consent" && o !== !0)) {
|
|
659
|
+
if (n.kind === "location") {
|
|
660
|
+
const d = {};
|
|
661
|
+
o.county && (d.county = o.county), o.where && (d.where = o.where), Array.isArray(o.gps) && o.gps.length === 2 && (d.gps = o.gps), Object.keys(d).length && (a[n.key] = d);
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
a[n.key] = o;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
formId: r.formId,
|
|
669
|
+
flowVersion: r.flowVersion,
|
|
670
|
+
path: be(e),
|
|
671
|
+
answers: a,
|
|
672
|
+
media: t.map(({ assetId: n, type: o }) => ({ assetId: n, type: o }))
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
function $e(r, e) {
|
|
676
|
+
if (r === 429)
|
|
677
|
+
return {
|
|
678
|
+
kind: "throttled",
|
|
679
|
+
message: e?.message || "You have reached the submission limit; please try again later.",
|
|
680
|
+
limit: e?.limit,
|
|
681
|
+
retryAfter: e?.retryAfter
|
|
682
|
+
};
|
|
683
|
+
if (r === 401)
|
|
684
|
+
return { kind: "auth", message: "Please sign in again to continue." };
|
|
685
|
+
if (r === 403)
|
|
686
|
+
return { kind: "forbidden", message: e?.message || "This submission belongs to another account." };
|
|
687
|
+
if (r === 404)
|
|
688
|
+
return { kind: "unknown-form", message: "This form is no longer available." };
|
|
689
|
+
if (r === 409)
|
|
690
|
+
return {
|
|
691
|
+
kind: "stale",
|
|
692
|
+
message: e?.message || "The form changed while you were filling it out; please review and resubmit."
|
|
693
|
+
};
|
|
694
|
+
if (r === 400 && e?.code === "media-not-verified")
|
|
695
|
+
return {
|
|
696
|
+
kind: "media",
|
|
697
|
+
message: e.message || "An attached file is missing or does not match this submission.",
|
|
698
|
+
assetId: e.assetId
|
|
699
|
+
};
|
|
700
|
+
if (r === 400 && Array.isArray(e)) {
|
|
701
|
+
const t = e[0] || {};
|
|
702
|
+
return {
|
|
703
|
+
kind: "validation",
|
|
704
|
+
message: "Something about your answers needs another look.",
|
|
705
|
+
fieldKey: String(t.instancePath || "").split("/").filter(Boolean)[0] || null
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
return r === 0 ? { kind: "network", message: "We could not reach WRAL; check your connection and try again." } : {
|
|
709
|
+
kind: "server",
|
|
710
|
+
message: "Something went wrong on our end; please try again."
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
const Fe = 34, Ie = 44;
|
|
714
|
+
function P(r, e) {
|
|
715
|
+
const t = String(r);
|
|
716
|
+
return t.length > e ? `${t.slice(0, e)}…` : t;
|
|
717
|
+
}
|
|
718
|
+
function Ee(r) {
|
|
719
|
+
return !r || typeof r != "object" ? "" : [r.county, r.where].filter(Boolean).join(", ");
|
|
720
|
+
}
|
|
721
|
+
function Ce(r, e = {}, t = []) {
|
|
722
|
+
const s = [];
|
|
723
|
+
for (const i of r) {
|
|
724
|
+
if (i.kind === "consent") continue;
|
|
725
|
+
if (i.kind === "media") {
|
|
726
|
+
t.length && s.push(`${t.length} ${t.length === 1 ? "file" : "files"}`);
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
const a = i.kind === "location" ? Ee(e[i.key]) : e[i.key];
|
|
730
|
+
a == null || a === "" || (i.kind === "textarea" ? s.push(`"${P(a, Ie)}"`) : s.push(P(a, Fe)));
|
|
731
|
+
}
|
|
732
|
+
return s.join(" · ") || "Details provided";
|
|
733
|
+
}
|
|
734
|
+
function Se(r, e = [], t = null) {
|
|
735
|
+
const s = [];
|
|
736
|
+
for (const a of e) {
|
|
737
|
+
const n = g(r, a.nodeId);
|
|
738
|
+
a.kind === "choice" && n && s.push({ k: (n.prompt || "").replace(/\?$/, ""), v: a.label || a.optionId }), a.kind === "collect" && s.push({ k: "Details", v: a.summary || "Details provided" }), a.kind === "gate" && (a.email || t) && s.push({ k: "Verified email", v: a.email || t });
|
|
739
|
+
}
|
|
740
|
+
const i = ye(U(r, e));
|
|
741
|
+
return i && s.push({ k: "Priority", v: i === "urgent" ? "Urgent" : "Moderate", pill: i }), s;
|
|
742
|
+
}
|
|
743
|
+
function Ae(r, e = []) {
|
|
744
|
+
const t = r?.confirmation || {};
|
|
745
|
+
return {
|
|
746
|
+
title: t.title || "Report received.",
|
|
747
|
+
body: t.body || "Your report is in the review queue.",
|
|
748
|
+
urgentNote: e.includes("urgent") ? t.urgentNote : void 0
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
function Re(r, e) {
|
|
752
|
+
const t = e === "failed" ? " The notification is queued for a delivery retry." : "";
|
|
753
|
+
return r?.startsWith("studio+email:") ? `Sent to the Studio review queue; the desk is emailed.${t}` : r?.startsWith("studio:") ? "Sent to the Studio review queue." : r?.startsWith("jsm:") ? `A ticket is opened for tech support with your details as fields, not prose.${t}` : r?.startsWith("email:") ? `Emailed to the owning desk with a link to your report.${t}` : "Routed to the right team.";
|
|
754
|
+
}
|
|
755
|
+
function De({ routing: r = {}, flags: e = [], reference: t = "" }) {
|
|
756
|
+
const s = e.includes("urgent");
|
|
757
|
+
return [
|
|
758
|
+
{
|
|
759
|
+
title: "Received",
|
|
760
|
+
body: `Report ${t} and its media are written to the dedicated Report It intake store before anything downstream touches them.`
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
title: "Routed",
|
|
764
|
+
body: Re(r.destination, r.state),
|
|
765
|
+
pill: s ? "Urgent · pinned first" : void 0
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
title: "Reviewed",
|
|
769
|
+
body: "An editor reviews it in the Report It queue: accept and promote with your credit, reject, or re-route."
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
title: "Monitored",
|
|
773
|
+
body: "The record joins the monitoring stream, which watches for related reports by category, place and symptom."
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
title: "Escalation check",
|
|
777
|
+
body: "Related reports crossing a threshold alert the owning team automatically; below it, we keep monitoring."
|
|
778
|
+
}
|
|
779
|
+
];
|
|
780
|
+
}
|
|
781
|
+
const E = "wral-report-it:draft:v1", L = 1;
|
|
782
|
+
function Te(r, e) {
|
|
783
|
+
try {
|
|
784
|
+
r.setItem(E, JSON.stringify({ ...e, v: L, savedAt: Date.now() }));
|
|
785
|
+
} catch {
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
function ze(r) {
|
|
789
|
+
try {
|
|
790
|
+
const e = r.getItem(E);
|
|
791
|
+
if (!e) return null;
|
|
792
|
+
const t = JSON.parse(e);
|
|
793
|
+
return t?.v !== L ? null : t;
|
|
794
|
+
} catch {
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
function B(r) {
|
|
799
|
+
try {
|
|
800
|
+
r.removeItem(E);
|
|
801
|
+
} catch {
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
function Pe({ apiOrigin: r, form: e, steps: t, requestId: s = null, mediaMeta: i = {} }) {
|
|
805
|
+
return {
|
|
806
|
+
apiOrigin: r,
|
|
807
|
+
formId: e.formId,
|
|
808
|
+
flowVersion: e.flowVersion,
|
|
809
|
+
requestId: s,
|
|
810
|
+
steps: t,
|
|
811
|
+
media: i
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function Be(r, e, { apiOrigin: t } = {}) {
|
|
815
|
+
if (!r) return { status: "discarded" };
|
|
816
|
+
if (t && r.apiOrigin && r.apiOrigin !== t)
|
|
817
|
+
return { status: "discarded" };
|
|
818
|
+
const s = (e || []).find((n) => n.formId === r.formId);
|
|
819
|
+
if (!s) return { status: "discarded" };
|
|
820
|
+
const { error: i } = M(s, r.steps || []);
|
|
821
|
+
if (i) return { status: "discarded" };
|
|
822
|
+
const a = {};
|
|
823
|
+
for (const [n, o] of Object.entries(r.media || {}))
|
|
824
|
+
o?.length && (a[n] = o.map((d) => ({ ...d, lost: !0 })));
|
|
825
|
+
return {
|
|
826
|
+
status: r.flowVersion === s.flowVersion ? "ok" : "review-required",
|
|
827
|
+
form: s,
|
|
828
|
+
steps: r.steps || [],
|
|
829
|
+
requestId: r.requestId || null,
|
|
830
|
+
lostMedia: a
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
function k(r = []) {
|
|
834
|
+
return {
|
|
835
|
+
stage: "idle",
|
|
836
|
+
resumeStage: null,
|
|
837
|
+
files: r.map((e) => ({
|
|
838
|
+
name: e.name,
|
|
839
|
+
type: e.type,
|
|
840
|
+
size: e.size,
|
|
841
|
+
assetId: null,
|
|
842
|
+
url: null,
|
|
843
|
+
assetUrl: null,
|
|
844
|
+
progress: 0,
|
|
845
|
+
status: "pending",
|
|
846
|
+
// pending | uploading | done | failed
|
|
847
|
+
error: null
|
|
848
|
+
})),
|
|
849
|
+
result: null,
|
|
850
|
+
error: null
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
function qe(r, e) {
|
|
854
|
+
switch (e.type) {
|
|
855
|
+
case "START":
|
|
856
|
+
return { ...r, stage: r.files.length ? "minting" : "requesting", error: null };
|
|
857
|
+
case "AUTH_REQUIRED":
|
|
858
|
+
return { ...r, resumeStage: r.stage === "auth" ? r.resumeStage : r.stage, stage: "auth" };
|
|
859
|
+
case "AUTH_OK":
|
|
860
|
+
return { ...r, stage: r.resumeStage || "idle", resumeStage: null };
|
|
861
|
+
case "MINTED": {
|
|
862
|
+
const t = r.files.map((s, i) => ({
|
|
863
|
+
...s,
|
|
864
|
+
assetId: e.uploads[i]?.assetId ?? s.assetId,
|
|
865
|
+
url: e.uploads[i]?.url ?? s.url,
|
|
866
|
+
assetUrl: e.uploads[i]?.assetUrl ?? s.assetUrl,
|
|
867
|
+
status: "uploading",
|
|
868
|
+
progress: 0,
|
|
869
|
+
error: null
|
|
870
|
+
}));
|
|
871
|
+
return { ...r, stage: "uploading", files: t };
|
|
872
|
+
}
|
|
873
|
+
case "FILE_PROGRESS":
|
|
874
|
+
return x(r, e.i, (t) => ({
|
|
875
|
+
...t,
|
|
876
|
+
progress: e.total ? e.loaded / e.total : 0
|
|
877
|
+
}));
|
|
878
|
+
case "FILE_DONE": {
|
|
879
|
+
const t = x(r, e.i, (s) => ({ ...s, status: "done", progress: 1 }));
|
|
880
|
+
return Me(t);
|
|
881
|
+
}
|
|
882
|
+
case "FILE_FAIL":
|
|
883
|
+
return { ...x(r, e.i, (s) => ({
|
|
884
|
+
...s,
|
|
885
|
+
status: "failed",
|
|
886
|
+
error: e.error || "upload failed"
|
|
887
|
+
})), stage: "uploading-error" };
|
|
888
|
+
case "RETRY_FILE":
|
|
889
|
+
return { ...x(r, e.i, (s) => ({
|
|
890
|
+
...s,
|
|
891
|
+
status: "uploading",
|
|
892
|
+
progress: 0,
|
|
893
|
+
error: null
|
|
894
|
+
})), stage: "uploading" };
|
|
895
|
+
case "REMINT_FILE":
|
|
896
|
+
return { ...x(r, e.i, (s) => ({
|
|
897
|
+
...s,
|
|
898
|
+
assetId: e.upload.assetId,
|
|
899
|
+
url: e.upload.url,
|
|
900
|
+
assetUrl: e.upload.assetUrl ?? s.assetUrl,
|
|
901
|
+
status: "uploading",
|
|
902
|
+
progress: 0,
|
|
903
|
+
error: null
|
|
904
|
+
})), stage: "uploading" };
|
|
905
|
+
case "REQUEST_START":
|
|
906
|
+
return { ...r, stage: "requesting", error: null };
|
|
907
|
+
case "REQUEST_OK":
|
|
908
|
+
return { ...r, stage: "done", result: e.result, error: null };
|
|
909
|
+
case "REQUEST_FAIL":
|
|
910
|
+
return { ...r, stage: "requesting-error", error: e.error };
|
|
911
|
+
case "RESET":
|
|
912
|
+
return k([]);
|
|
913
|
+
default:
|
|
914
|
+
return r;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function x(r, e, t) {
|
|
918
|
+
const s = r.files.map((i, a) => a === e ? t(i) : i);
|
|
919
|
+
return { ...r, files: s };
|
|
920
|
+
}
|
|
921
|
+
function Me(r) {
|
|
922
|
+
const e = r.files.every((s) => s.status === "done"), t = r.files.some((s) => s.status === "failed");
|
|
923
|
+
return e ? { ...r, stage: "requesting" } : t ? { ...r, stage: "uploading-error" } : r;
|
|
924
|
+
}
|
|
925
|
+
function q(r) {
|
|
926
|
+
return r.files.map((e, t) => ({ ...e, i: t })).filter((e) => e.status === "failed");
|
|
927
|
+
}
|
|
928
|
+
const Ue = [
|
|
929
|
+
"Wake",
|
|
930
|
+
"Durham",
|
|
931
|
+
"Orange",
|
|
932
|
+
"Johnston",
|
|
933
|
+
"Cumberland",
|
|
934
|
+
"Harnett",
|
|
935
|
+
"Chatham",
|
|
936
|
+
"Franklin",
|
|
937
|
+
"Granville",
|
|
938
|
+
"Nash",
|
|
939
|
+
"Wayne",
|
|
940
|
+
"Wilson",
|
|
941
|
+
"Lee",
|
|
942
|
+
"Moore",
|
|
943
|
+
"Sampson",
|
|
944
|
+
"Other / outside the area"
|
|
945
|
+
], je = "https://api.wral.com/forms", Le = 480, Oe = 2;
|
|
946
|
+
class Qe extends I {
|
|
947
|
+
static styles = de;
|
|
948
|
+
static properties = {
|
|
949
|
+
api: { type: String },
|
|
950
|
+
_phase: { state: !0 },
|
|
951
|
+
_forms: { state: !0 },
|
|
952
|
+
_form: { state: !0 },
|
|
953
|
+
_steps: { state: !0 },
|
|
954
|
+
_typing: { state: !0 },
|
|
955
|
+
_cardValues: { state: !0 },
|
|
956
|
+
_fieldErrors: { state: !0 },
|
|
957
|
+
_gate: { state: !0 },
|
|
958
|
+
_submit: { state: !0 },
|
|
959
|
+
_banner: { state: !0 },
|
|
960
|
+
_notice: { state: !0 },
|
|
961
|
+
_result: { state: !0 }
|
|
962
|
+
};
|
|
963
|
+
#o = null;
|
|
964
|
+
#a = null;
|
|
965
|
+
#u = null;
|
|
966
|
+
#t = null;
|
|
967
|
+
#r = {};
|
|
968
|
+
#s = {};
|
|
969
|
+
#i = /* @__PURE__ */ new Map();
|
|
970
|
+
#g = null;
|
|
971
|
+
#m = !1;
|
|
972
|
+
#l = null;
|
|
973
|
+
constructor() {
|
|
974
|
+
super(), this.api = je, this._phase = "loading", this._forms = [], this._form = null, this._steps = [], this._typing = !1, this._cardValues = {}, this._fieldErrors = {}, this._gate = { stage: null, email: "", error: "", loading: !1 }, this._submit = k([]), this._banner = null, this._notice = null, this._result = null;
|
|
975
|
+
}
|
|
976
|
+
get client() {
|
|
977
|
+
return this.#o ? this.#o : ((!this.#a || this.#u !== this.api) && (this.#a = fe({ baseUrl: this.api }), this.#u = this.api), this.#a);
|
|
978
|
+
}
|
|
979
|
+
set client(e) {
|
|
980
|
+
this.#o = e || null, this.isConnected && this.#b();
|
|
981
|
+
}
|
|
982
|
+
connectedCallback() {
|
|
983
|
+
super.connectedCallback(), this.#b();
|
|
984
|
+
}
|
|
985
|
+
disconnectedCallback() {
|
|
986
|
+
super.disconnectedCallback(), clearTimeout(this.#g), this.#l?.();
|
|
987
|
+
for (const { abort: e } of this.#i.values()) e?.();
|
|
988
|
+
this.#i.clear();
|
|
989
|
+
}
|
|
990
|
+
get #A() {
|
|
991
|
+
try {
|
|
992
|
+
return matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
993
|
+
} catch {
|
|
994
|
+
return !0;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
get #p() {
|
|
998
|
+
try {
|
|
999
|
+
return sessionStorage;
|
|
1000
|
+
} catch {
|
|
1001
|
+
return { getItem: () => null, setItem: () => {
|
|
1002
|
+
}, removeItem: () => {
|
|
1003
|
+
} };
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
// ── Loading + draft restore ─────────────────────────────────────
|
|
1007
|
+
async #b() {
|
|
1008
|
+
this._phase = "loading", this._banner = null;
|
|
1009
|
+
try {
|
|
1010
|
+
this._forms = await this.client.listForms();
|
|
1011
|
+
} catch {
|
|
1012
|
+
this._phase = "load-error";
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const e = Be(ze(this.#p), this._forms, { apiOrigin: this.api });
|
|
1016
|
+
if (e.status === "ok" || e.status === "review-required") {
|
|
1017
|
+
this._form = e.form, this._steps = e.steps, this.#t = e.requestId, this.#s = e.lostMedia || {}, e.status === "review-required" ? this._notice = "This form changed since your last visit — please review your answers." : Object.keys(this.#s).length && (this._notice = "Welcome back — your answers were saved, but attachments need to be re-attached."), this.#n({ animate: !1 });
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
this._phase = "pick";
|
|
1021
|
+
}
|
|
1022
|
+
#d() {
|
|
1023
|
+
if (!this._form) return;
|
|
1024
|
+
const e = {};
|
|
1025
|
+
for (const [t, s] of Object.entries(this.#r))
|
|
1026
|
+
s.length && (e[t] = s.map(({ name: i, type: a, size: n }) => ({ name: i, type: a, size: n })));
|
|
1027
|
+
Te(this.#p, Pe({
|
|
1028
|
+
apiOrigin: this.api,
|
|
1029
|
+
form: this._form,
|
|
1030
|
+
steps: this._steps,
|
|
1031
|
+
requestId: this.#t,
|
|
1032
|
+
mediaMeta: e
|
|
1033
|
+
}));
|
|
1034
|
+
}
|
|
1035
|
+
// ── Walk mechanics ──────────────────────────────────────────────
|
|
1036
|
+
#n({ animate: e = !0 } = {}) {
|
|
1037
|
+
this._phase = "walk", this._cardValues = {}, this._fieldErrors = {}, this._gate = { stage: null, email: "", error: "", loading: !1 }, e && !this.#A ? (this._typing = !0, clearTimeout(this.#g), this.#g = setTimeout(() => {
|
|
1038
|
+
this._typing = !1;
|
|
1039
|
+
}, Le)) : this._typing = !1;
|
|
1040
|
+
}
|
|
1041
|
+
get #_() {
|
|
1042
|
+
return this._form ? M(this._form, this._steps) : { node: null };
|
|
1043
|
+
}
|
|
1044
|
+
#R(e) {
|
|
1045
|
+
this._form = e, this._steps = [], this.#t = null, this.#r = {}, this.#s = {}, this._notice = null, this.#d(), this.#n();
|
|
1046
|
+
}
|
|
1047
|
+
#y(e) {
|
|
1048
|
+
this._steps = [...this._steps, e], this.#d(), this.#n();
|
|
1049
|
+
}
|
|
1050
|
+
#D() {
|
|
1051
|
+
if (this._phase === "done" || this._phase === "submitting") return;
|
|
1052
|
+
if (this._phase === "review") {
|
|
1053
|
+
this.#n({ animate: !1 });
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
if (!this._steps.length) {
|
|
1057
|
+
this.#w();
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
const e = this._steps.at(-1);
|
|
1061
|
+
this._steps = this._steps.slice(0, -1), e.kind === "collect" && (this._cardValues = { ...e.values }), this.#d(), this.#n({ animate: !1 });
|
|
1062
|
+
}
|
|
1063
|
+
#w() {
|
|
1064
|
+
this._form = null, this._steps = [], this.#t = null, this.#r = {}, this.#s = {}, this._result = null, this._banner = null, this._notice = null, this._submit = k([]), B(this.#p), this._phase = "pick";
|
|
1065
|
+
}
|
|
1066
|
+
// ── Collect card ────────────────────────────────────────────────
|
|
1067
|
+
#x(e, t) {
|
|
1068
|
+
this._cardValues = { ...this._cardValues, [e]: t };
|
|
1069
|
+
}
|
|
1070
|
+
#T(e) {
|
|
1071
|
+
const t = e.fields || [], { ok: s, errors: i } = T(t, this._cardValues), a = t.find((d) => d.kind === "media");
|
|
1072
|
+
let n = !0;
|
|
1073
|
+
if (a) {
|
|
1074
|
+
const d = this.#r[e.id] || [], f = F(d, this._form.budget, {
|
|
1075
|
+
required: !!a.required
|
|
1076
|
+
});
|
|
1077
|
+
f.ok ? d.length && (this.#s = { ...this.#s, [e.id]: [] }) : (n = !1, i[a.key] = f.errors[0].message);
|
|
1078
|
+
}
|
|
1079
|
+
if (!s || !n) {
|
|
1080
|
+
this._fieldErrors = i;
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
const o = this.#r[e.id] || [];
|
|
1084
|
+
this.#y({
|
|
1085
|
+
nodeId: e.id,
|
|
1086
|
+
kind: "collect",
|
|
1087
|
+
values: { ...this._cardValues },
|
|
1088
|
+
summary: Ce(t, this._cardValues, o)
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
// ── Gate ────────────────────────────────────────────────────────
|
|
1092
|
+
async #$() {
|
|
1093
|
+
if (!(this._gate.stage || this.#m)) {
|
|
1094
|
+
this.#m = !0;
|
|
1095
|
+
try {
|
|
1096
|
+
const e = await this.client.isAuthenticated();
|
|
1097
|
+
this._gate = e ? { stage: "signedin", email: this.client.signedInEmail?.() || "", error: "", loading: !1 } : { stage: "email", email: "", error: "", loading: !1 };
|
|
1098
|
+
} finally {
|
|
1099
|
+
this.#m = !1;
|
|
1100
|
+
}
|
|
1101
|
+
this._gate.stage === "email" && this.client.onCrossTabLogin && !this.#l && (this.#l = this.client.onCrossTabLogin(() => {
|
|
1102
|
+
this.#l?.(), this.#l = null, this._gate = { stage: null, email: "", error: "", loading: !1 }, this.#$();
|
|
1103
|
+
}));
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
async #v() {
|
|
1107
|
+
const e = this._gate.email.trim();
|
|
1108
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)) {
|
|
1109
|
+
this._gate = { ...this._gate, error: "Please enter a valid email address." };
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
this._gate = { ...this._gate, error: "", loading: !0 };
|
|
1113
|
+
try {
|
|
1114
|
+
const t = await this.client.sendCode(e);
|
|
1115
|
+
this._gate = t.authenticated ? { stage: "signedin", email: e, error: "", loading: !1 } : { ...this._gate, stage: "code", loading: !1 };
|
|
1116
|
+
} catch (t) {
|
|
1117
|
+
this._gate = {
|
|
1118
|
+
...this._gate,
|
|
1119
|
+
loading: !1,
|
|
1120
|
+
error: t.message || "We could not send a code; please try again."
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
async #F(e) {
|
|
1125
|
+
this._gate = { ...this._gate, error: "", loading: !0 };
|
|
1126
|
+
try {
|
|
1127
|
+
await this.client.verifyCode(e), this.#I(this._gate.email);
|
|
1128
|
+
} catch (t) {
|
|
1129
|
+
this._gate = {
|
|
1130
|
+
...this._gate,
|
|
1131
|
+
loading: !1,
|
|
1132
|
+
error: t.message || "Invalid or expired code."
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
#I(e) {
|
|
1137
|
+
if (this._gate = { stage: null, email: "", error: "", loading: !1 }, this._submit.stage === "auth") {
|
|
1138
|
+
this.#e({ type: "AUTH_OK" }), this._phase = "submitting", this.#c();
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const { node: t } = this.#_;
|
|
1142
|
+
t?.type === "gate" && this.#y({ nodeId: t.id, kind: "gate", email: e });
|
|
1143
|
+
}
|
|
1144
|
+
#z() {
|
|
1145
|
+
this.client.signOut?.(), this._gate = { stage: "email", email: "", error: "", loading: !1 };
|
|
1146
|
+
}
|
|
1147
|
+
// ── Submit pipeline ─────────────────────────────────────────────
|
|
1148
|
+
get #h() {
|
|
1149
|
+
const e = [];
|
|
1150
|
+
for (const t of this._steps)
|
|
1151
|
+
if (t.kind === "collect")
|
|
1152
|
+
for (const s of this.#r[t.nodeId] || [])
|
|
1153
|
+
e.push(s);
|
|
1154
|
+
return e;
|
|
1155
|
+
}
|
|
1156
|
+
#e(e) {
|
|
1157
|
+
this._submit = qe(this._submit, e);
|
|
1158
|
+
}
|
|
1159
|
+
async #P() {
|
|
1160
|
+
this._banner = null;
|
|
1161
|
+
for (let e = 0; e < this._steps.length; e++) {
|
|
1162
|
+
const t = this._steps[e];
|
|
1163
|
+
if (t.kind !== "collect") continue;
|
|
1164
|
+
const s = g(this._form, t.nodeId), { ok: i, errors: a } = T(s?.fields || [], t.values), n = (s?.fields || []).find((d) => d.kind === "media");
|
|
1165
|
+
let o = !0;
|
|
1166
|
+
if (n) {
|
|
1167
|
+
const d = this.#r[t.nodeId] || [], f = F(d, this._form.budget, {
|
|
1168
|
+
required: !!n.required
|
|
1169
|
+
});
|
|
1170
|
+
f.ok || (o = !1, a[n.key] = f.errors[0].message);
|
|
1171
|
+
}
|
|
1172
|
+
if (!i || !o) {
|
|
1173
|
+
this._steps = this._steps.slice(0, e), this._cardValues = { ...t.values }, this._fieldErrors = a, this.#d(), this.#n({ animate: !1 });
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
if (this.#t = this.#t || crypto.randomUUID(), this.#d(), this._submit = k(this.#h), this._phase = "submitting", !await this.client.isAuthenticated()) {
|
|
1178
|
+
this.#e({ type: "START" }), this.#e({ type: "AUTH_REQUIRED" }), this._gate = { stage: "email", email: "", error: "", loading: !1 };
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
this.#e({ type: "START" }), this.#c();
|
|
1182
|
+
}
|
|
1183
|
+
async #c() {
|
|
1184
|
+
const e = this._submit.stage;
|
|
1185
|
+
if (e === "minting") return this.#B();
|
|
1186
|
+
if (e === "uploading") return this.#E();
|
|
1187
|
+
if (e === "requesting") return this.#U();
|
|
1188
|
+
}
|
|
1189
|
+
async #B() {
|
|
1190
|
+
try {
|
|
1191
|
+
const e = await this.client.createUploads(z({
|
|
1192
|
+
formId: this._form.formId,
|
|
1193
|
+
requestId: this.#t,
|
|
1194
|
+
files: this.#h
|
|
1195
|
+
}));
|
|
1196
|
+
this.#e({ type: "MINTED", uploads: e }), this.#c();
|
|
1197
|
+
} catch (e) {
|
|
1198
|
+
this.#k(e);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
#E() {
|
|
1202
|
+
const e = this._submit.files, t = this.#i.size;
|
|
1203
|
+
let s = 0;
|
|
1204
|
+
for (let i = 0; i < e.length && t + s < Oe; i++)
|
|
1205
|
+
e[i].status !== "uploading" || this.#i.has(i) || (s += 1, this.#q(i));
|
|
1206
|
+
}
|
|
1207
|
+
#q(e) {
|
|
1208
|
+
const t = this._submit.files[e], s = this.#h[e], { promise: i, abort: a } = this.client.uploadFile({
|
|
1209
|
+
url: t.url,
|
|
1210
|
+
file: s,
|
|
1211
|
+
contentType: t.type,
|
|
1212
|
+
onProgress: ({ loaded: n, total: o }) => {
|
|
1213
|
+
this.#e({ type: "FILE_PROGRESS", i: e, loaded: n, total: o });
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
this.#i.set(e, { abort: a }), i.then(() => {
|
|
1217
|
+
this.#i.delete(e), this.#e({ type: "FILE_DONE", i: e }), this.#c();
|
|
1218
|
+
}).catch((n) => {
|
|
1219
|
+
this.#i.delete(e), this.#e({
|
|
1220
|
+
type: "FILE_FAIL",
|
|
1221
|
+
i: e,
|
|
1222
|
+
error: n?.status === 403 ? "expired" : "failed"
|
|
1223
|
+
});
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
async #M(e) {
|
|
1227
|
+
if (this._submit.files[e].error === "expired")
|
|
1228
|
+
try {
|
|
1229
|
+
const [s] = await this.client.createUploads(z({
|
|
1230
|
+
formId: this._form.formId,
|
|
1231
|
+
requestId: this.#t,
|
|
1232
|
+
files: [this.#h[e]]
|
|
1233
|
+
}));
|
|
1234
|
+
this.#e({ type: "REMINT_FILE", i: e, upload: s });
|
|
1235
|
+
} catch (s) {
|
|
1236
|
+
this.#k(s);
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
else
|
|
1240
|
+
this.#e({ type: "RETRY_FILE", i: e });
|
|
1241
|
+
this.#E();
|
|
1242
|
+
}
|
|
1243
|
+
async #U() {
|
|
1244
|
+
try {
|
|
1245
|
+
const e = this._submit.files.map(({ assetId: s, type: i }) => ({ assetId: s, type: i })), t = await this.client.putRequest(this.#t, _e({
|
|
1246
|
+
form: this._form,
|
|
1247
|
+
steps: this._steps,
|
|
1248
|
+
media: e
|
|
1249
|
+
}));
|
|
1250
|
+
this.#e({ type: "REQUEST_OK", result: t }), this._result = t, B(this.#p), this._phase = "done";
|
|
1251
|
+
} catch (e) {
|
|
1252
|
+
this.#k(e);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
#k(e) {
|
|
1256
|
+
const t = $e(e?.status ?? 0, e?.body ?? null);
|
|
1257
|
+
if (t.kind === "auth") {
|
|
1258
|
+
this.#e({ type: "AUTH_REQUIRED" }), this._gate = { stage: "email", email: "", error: "", loading: !1 };
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
if (t.kind === "stale") {
|
|
1262
|
+
this._banner = t, this.#e({ type: "REQUEST_FAIL", error: t }), this.#j();
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
this._banner = t, this.#e({ type: "REQUEST_FAIL", error: t });
|
|
1266
|
+
}
|
|
1267
|
+
async #j() {
|
|
1268
|
+
try {
|
|
1269
|
+
this._forms = await this.client.listForms();
|
|
1270
|
+
const e = this._forms.find((t) => t.formId === this._form.formId);
|
|
1271
|
+
e && (this._form = e);
|
|
1272
|
+
} catch {
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
#L() {
|
|
1276
|
+
this._banner = null, this.#e({ type: "REQUEST_START" }), this.#c();
|
|
1277
|
+
}
|
|
1278
|
+
// ── Render ──────────────────────────────────────────────────────
|
|
1279
|
+
render() {
|
|
1280
|
+
return l`
|
|
1281
|
+
<div class="chat" aria-live="polite">
|
|
1282
|
+
${this.#O()}
|
|
1283
|
+
</div>
|
|
1284
|
+
${this.#Z()}
|
|
1285
|
+
`;
|
|
1286
|
+
}
|
|
1287
|
+
#O() {
|
|
1288
|
+
switch (this._phase) {
|
|
1289
|
+
case "loading":
|
|
1290
|
+
return $();
|
|
1291
|
+
case "load-error":
|
|
1292
|
+
return l`
|
|
1293
|
+
${h("We could not load the forms.", "Check your connection and try again.", { error: !0 })}
|
|
1294
|
+
<div class="options">
|
|
1295
|
+
<button type="button" class="chip" @click=${() => this.#b()}>Try again</button>
|
|
1296
|
+
</div>
|
|
1297
|
+
`;
|
|
1298
|
+
case "pick":
|
|
1299
|
+
return this.#V();
|
|
1300
|
+
case "walk":
|
|
1301
|
+
return l`${this.#f()}${this._typing ? $() : this.#W()}`;
|
|
1302
|
+
case "review":
|
|
1303
|
+
return l`${this.#f()}${this.#S()}`;
|
|
1304
|
+
case "submitting":
|
|
1305
|
+
return l`${this.#f()}${this.#J()}`;
|
|
1306
|
+
case "done":
|
|
1307
|
+
return l`${this.#f()}${this.#X()}`;
|
|
1308
|
+
default:
|
|
1309
|
+
return c;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
#V() {
|
|
1313
|
+
const e = this._forms;
|
|
1314
|
+
return l`
|
|
1315
|
+
${h(
|
|
1316
|
+
"Welcome to Report It. What would you like to send us?",
|
|
1317
|
+
"A few quick questions route your report to the right team."
|
|
1318
|
+
)}
|
|
1319
|
+
<div class="options" role="group" aria-label="What would you like to send us?">
|
|
1320
|
+
${e.map((t, s) => l`
|
|
1321
|
+
<button type="button" class="chip" style="animation-delay: ${60 + s * 50}ms"
|
|
1322
|
+
@click=${() => this.#R(t)}>${t.title}</button>
|
|
1323
|
+
`)}
|
|
1324
|
+
</div>
|
|
1325
|
+
`;
|
|
1326
|
+
}
|
|
1327
|
+
#f() {
|
|
1328
|
+
const e = [];
|
|
1329
|
+
this._form && (e.push(h(
|
|
1330
|
+
"Welcome to Report It. What would you like to send us?",
|
|
1331
|
+
"A few quick questions route your report to the right team.",
|
|
1332
|
+
{ noAnim: !0 }
|
|
1333
|
+
)), e.push(v(this._form.title, { noAnim: !0 }))), this._notice && e.push(h(this._notice, "", { noAnim: !0 }));
|
|
1334
|
+
for (const t of this._steps) {
|
|
1335
|
+
const s = g(this._form, t.nodeId);
|
|
1336
|
+
s?.prompt && e.push(h(s.prompt, s.help, { noAnim: !0 })), s?.type === "gate" && e.push(h(
|
|
1337
|
+
"Last step: verify your email so we can follow up.",
|
|
1338
|
+
"One address, one code. No password needed.",
|
|
1339
|
+
{ noAnim: !0 }
|
|
1340
|
+
)), t.kind === "choice" && e.push(v(t.label || t.optionId, { noAnim: !0 })), t.kind === "collect" && e.push(v(t.summary || "Details provided", { noAnim: !0 })), t.kind === "gate" && e.push(v(t.email, { noAnim: !0 }));
|
|
1341
|
+
}
|
|
1342
|
+
return e;
|
|
1343
|
+
}
|
|
1344
|
+
#W() {
|
|
1345
|
+
const { node: e, error: t } = this.#_;
|
|
1346
|
+
return t || !e ? h("Something went sideways with this form.", "Please start over.", { error: !0 }) : e.type === "choice" ? this.#N(e) : e.type === "collect" ? this.#H(e) : e.type === "gate" ? this.#C(e) : e.type === "redirect" ? this.#G(e) : e.type === "terminal" ? this.#S() : c;
|
|
1347
|
+
}
|
|
1348
|
+
#N(e) {
|
|
1349
|
+
return l`
|
|
1350
|
+
${h(e.prompt, e.help)}
|
|
1351
|
+
<div class="options" role="group" aria-label=${e.prompt || "Choose one"}>
|
|
1352
|
+
${(e.options || []).map((t, s) => l`
|
|
1353
|
+
<button type="button" class="chip" style="animation-delay: ${60 + s * 50}ms"
|
|
1354
|
+
@click=${() => this.#y({
|
|
1355
|
+
nodeId: e.id,
|
|
1356
|
+
kind: "choice",
|
|
1357
|
+
optionId: t.id,
|
|
1358
|
+
label: t.label
|
|
1359
|
+
})}>${t.label}</button>
|
|
1360
|
+
`)}
|
|
1361
|
+
</div>
|
|
1362
|
+
`;
|
|
1363
|
+
}
|
|
1364
|
+
#H(e) {
|
|
1365
|
+
const t = e.fields || [];
|
|
1366
|
+
return l`
|
|
1367
|
+
${h(e.prompt, e.help)}
|
|
1368
|
+
<div class="form-card">
|
|
1369
|
+
${t.map((s) => this.#Y(e, s))}
|
|
1370
|
+
<button type="button" class="btn-primary" @click=${() => this.#T(e)}>
|
|
1371
|
+
Continue
|
|
1372
|
+
</button>
|
|
1373
|
+
</div>
|
|
1374
|
+
`;
|
|
1375
|
+
}
|
|
1376
|
+
#Y(e, t) {
|
|
1377
|
+
const s = this._cardValues[t.key], i = this._fieldErrors[t.key], a = !t.required && t.kind !== "consent" ? l` <span class="opt">optional</span>` : c, n = t.kind === "consent" ? c : l`
|
|
1378
|
+
<label for="f-${t.key}">${t.label}${a}</label>
|
|
1379
|
+
`;
|
|
1380
|
+
return l`
|
|
1381
|
+
<div class="field">
|
|
1382
|
+
${n}
|
|
1383
|
+
${this.#K(e, t, s)}
|
|
1384
|
+
${i ? l`<div class="err" role="alert">${i}</div>` : c}
|
|
1385
|
+
</div>
|
|
1386
|
+
`;
|
|
1387
|
+
}
|
|
1388
|
+
#K(e, t, s) {
|
|
1389
|
+
const i = (a) => (n) => this.#x(a, n.target.value);
|
|
1390
|
+
switch (t.kind) {
|
|
1391
|
+
case "text":
|
|
1392
|
+
return l`<input type="text" id="f-${t.key}" .value=${s || ""}
|
|
1393
|
+
placeholder=${t.placeholder || ""} @input=${i(t.key)}>`;
|
|
1394
|
+
case "url":
|
|
1395
|
+
return l`<input type="url" id="f-${t.key}" .value=${s || ""}
|
|
1396
|
+
placeholder=${t.placeholder || ""} @input=${i(t.key)}>`;
|
|
1397
|
+
case "textarea":
|
|
1398
|
+
return l`<textarea id="f-${t.key}" .value=${s || ""}
|
|
1399
|
+
placeholder=${t.placeholder || ""} @input=${i(t.key)}></textarea>`;
|
|
1400
|
+
case "datetime":
|
|
1401
|
+
return l`<input type="datetime-local" id="f-${t.key}" .value=${s || ""}
|
|
1402
|
+
@input=${i(t.key)}>`;
|
|
1403
|
+
case "select":
|
|
1404
|
+
return l`
|
|
1405
|
+
<select id="f-${t.key}" @change=${i(t.key)}>
|
|
1406
|
+
<option value="">Choose one</option>
|
|
1407
|
+
${(t.options || []).map((a) => l`
|
|
1408
|
+
<option value=${a.id} ?selected=${s === a.id}>${a.label}</option>
|
|
1409
|
+
`)}
|
|
1410
|
+
</select>
|
|
1411
|
+
`;
|
|
1412
|
+
case "location":
|
|
1413
|
+
return this.#Q(t, s || {});
|
|
1414
|
+
case "consent":
|
|
1415
|
+
return l`
|
|
1416
|
+
<div class="consent-row">
|
|
1417
|
+
<input type="checkbox" id="f-${t.key}" .checked=${s === !0}
|
|
1418
|
+
@change=${(a) => this.#x(t.key, a.target.checked)}>
|
|
1419
|
+
<label for="f-${t.key}" style="all: unset; cursor: pointer;">
|
|
1420
|
+
I took or own this material and give WRAL permission to use it
|
|
1421
|
+
on air and online with credit to me.
|
|
1422
|
+
</label>
|
|
1423
|
+
</div>
|
|
1424
|
+
`;
|
|
1425
|
+
case "media":
|
|
1426
|
+
return l`
|
|
1427
|
+
<wral-report-media
|
|
1428
|
+
.files=${this.#r[e.id] || []}
|
|
1429
|
+
.lostMeta=${this.#s[e.id] || []}
|
|
1430
|
+
.budget=${this._form.budget || {}}
|
|
1431
|
+
?required=${!!t.required}
|
|
1432
|
+
label=${t.label}
|
|
1433
|
+
@files-changed=${(a) => {
|
|
1434
|
+
this.#r = { ...this.#r, [e.id]: a.detail.files }, this.requestUpdate();
|
|
1435
|
+
}}></wral-report-media>
|
|
1436
|
+
`;
|
|
1437
|
+
default:
|
|
1438
|
+
return c;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
#Q(e, t) {
|
|
1442
|
+
const s = e.counties || Ue, i = (a) => this.#x(e.key, { ...t, ...a });
|
|
1443
|
+
return l`
|
|
1444
|
+
<select id="f-${e.key}" aria-label="County"
|
|
1445
|
+
@change=${(a) => i({ county: a.target.value || void 0 })}>
|
|
1446
|
+
<option value="">Choose a county</option>
|
|
1447
|
+
${s.map((a) => l`
|
|
1448
|
+
<option value=${a} ?selected=${t.county === a}>${a}</option>
|
|
1449
|
+
`)}
|
|
1450
|
+
</select>
|
|
1451
|
+
<input type="text" style="margin-top: 8px;"
|
|
1452
|
+
aria-label="Nearest town or crossroads"
|
|
1453
|
+
placeholder="Nearest town or crossroads"
|
|
1454
|
+
.value=${t.where || ""}
|
|
1455
|
+
@input=${(a) => i({ where: a.target.value || void 0 })}>
|
|
1456
|
+
`;
|
|
1457
|
+
}
|
|
1458
|
+
#C(e) {
|
|
1459
|
+
this.#$();
|
|
1460
|
+
const t = this._gate, s = h(
|
|
1461
|
+
"Last step: verify your email so we can follow up.",
|
|
1462
|
+
"One address, one code. No password needed."
|
|
1463
|
+
);
|
|
1464
|
+
return t.stage ? t.stage === "signedin" ? l`
|
|
1465
|
+
${s}
|
|
1466
|
+
<div class="form-card">
|
|
1467
|
+
<p style="margin: 0 0 12px; font-size: 14.5px;">
|
|
1468
|
+
You are signed in as <strong>${t.email || "your My WRAL account"}</strong> on this device.
|
|
1469
|
+
</p>
|
|
1470
|
+
<button type="button" class="btn-primary"
|
|
1471
|
+
@click=${() => this.#I(t.email)}>Continue</button>
|
|
1472
|
+
<button type="button" class="btn-ghost"
|
|
1473
|
+
@click=${() => this.#z()}>Use a different email</button>
|
|
1474
|
+
</div>
|
|
1475
|
+
` : t.stage === "email" ? l`
|
|
1476
|
+
${s}
|
|
1477
|
+
<div class="form-card login-card">
|
|
1478
|
+
<h2>Sign in or create account</h2>
|
|
1479
|
+
<p class="login-sub">
|
|
1480
|
+
Enter your email to get started. If you don’t have an account, we’ll create one for you.
|
|
1481
|
+
</p>
|
|
1482
|
+
<div class="field">
|
|
1483
|
+
<label for="gate-email">Email address</label>
|
|
1484
|
+
<input type="email" id="gate-email" autocomplete="email"
|
|
1485
|
+
placeholder="you@example.com" .value=${t.email}
|
|
1486
|
+
@input=${(i) => {
|
|
1487
|
+
this._gate = { ...t, email: i.target.value };
|
|
1488
|
+
}}
|
|
1489
|
+
@keydown=${(i) => {
|
|
1490
|
+
i.key === "Enter" && this.#v();
|
|
1491
|
+
}}>
|
|
1492
|
+
</div>
|
|
1493
|
+
${t.error ? l`<div class="err" role="alert">${t.error}</div>` : c}
|
|
1494
|
+
<button type="button" class="login-btn" ?disabled=${t.loading}
|
|
1495
|
+
@click=${() => this.#v()}>
|
|
1496
|
+
${t.loading ? "Sending…" : "Continue with email"}
|
|
1497
|
+
</button>
|
|
1498
|
+
<div class="login-why">
|
|
1499
|
+
<h3>Why create an account?</h3>
|
|
1500
|
+
<ul>
|
|
1501
|
+
<li>We can follow up on your report</li>
|
|
1502
|
+
<li>Your contact information fills in automatically next time</li>
|
|
1503
|
+
<li>You are credited by name when we use your photos or video</li>
|
|
1504
|
+
</ul>
|
|
1505
|
+
</div>
|
|
1506
|
+
</div>
|
|
1507
|
+
` : l`
|
|
1508
|
+
${s}
|
|
1509
|
+
<div class="form-card login-card">
|
|
1510
|
+
<h2>Enter verification code</h2>
|
|
1511
|
+
<p class="login-sub">We sent a 6-digit code to <strong>${t.email}</strong></p>
|
|
1512
|
+
<button type="button" class="change-email"
|
|
1513
|
+
@click=${() => {
|
|
1514
|
+
this._gate = { ...t, stage: "email", error: "" };
|
|
1515
|
+
}}>
|
|
1516
|
+
← Change email
|
|
1517
|
+
</button>
|
|
1518
|
+
<div class="field">
|
|
1519
|
+
<label>Verification code</label>
|
|
1520
|
+
<wral-code-input
|
|
1521
|
+
@code-complete=${(i) => this.#F(i.detail?.code ?? i.detail?.value ?? i.target.value)}>
|
|
1522
|
+
</wral-code-input>
|
|
1523
|
+
</div>
|
|
1524
|
+
${t.error ? l`<div class="err" role="alert">${t.error}</div>` : c}
|
|
1525
|
+
<button type="button" class="login-btn" ?disabled=${t.loading}
|
|
1526
|
+
@click=${() => {
|
|
1527
|
+
const i = this.renderRoot.querySelector("wral-code-input");
|
|
1528
|
+
this.#F(i?.value || "");
|
|
1529
|
+
}}>
|
|
1530
|
+
${t.loading ? "Checking…" : "Sign in"}
|
|
1531
|
+
</button>
|
|
1532
|
+
<p class="login-help">
|
|
1533
|
+
Didn’t receive a code? Check your spam folder or
|
|
1534
|
+
<button type="button" @click=${() => this.#v()}>resend a new code</button>.
|
|
1535
|
+
</p>
|
|
1536
|
+
</div>
|
|
1537
|
+
` : l`${s}${$()}`;
|
|
1538
|
+
}
|
|
1539
|
+
#G(e) {
|
|
1540
|
+
const t = e.redirect || {};
|
|
1541
|
+
return l`
|
|
1542
|
+
<div class="form-card">
|
|
1543
|
+
<p style="margin: 0 0 6px; font-weight: 600; font-size: 16px;
|
|
1544
|
+
color: var(--wral-base-primary-1, #001D68);
|
|
1545
|
+
font-family: var(--wral-base-font-ui, sans-serif);">
|
|
1546
|
+
${t.title || "This request uses a dedicated form"}
|
|
1547
|
+
</p>
|
|
1548
|
+
<p style="margin: 0 0 14px; font-size: 14.5px; color: var(--wral-semantic-fg-muted, #475569);">
|
|
1549
|
+
${t.body || ""}
|
|
1550
|
+
</p>
|
|
1551
|
+
<a class="btn-primary" style="display: inline-block; text-decoration: none;"
|
|
1552
|
+
href=${t.url || "#"} target="_blank" rel="noopener">
|
|
1553
|
+
Open the request form
|
|
1554
|
+
</a>
|
|
1555
|
+
</div>
|
|
1556
|
+
`;
|
|
1557
|
+
}
|
|
1558
|
+
#S() {
|
|
1559
|
+
const e = Se(this._form, this._steps);
|
|
1560
|
+
return l`
|
|
1561
|
+
${h("Here is what you are sending.", "Check it over, then submit.")}
|
|
1562
|
+
<div class="form-card">
|
|
1563
|
+
<ul class="review-list">
|
|
1564
|
+
${e.map((t) => l`
|
|
1565
|
+
<li>
|
|
1566
|
+
<span class="k">${t.k}</span>
|
|
1567
|
+
<span>${t.pill ? ce(t.pill) : t.v}</span>
|
|
1568
|
+
</li>
|
|
1569
|
+
`)}
|
|
1570
|
+
</ul>
|
|
1571
|
+
<div style="margin-top: 16px;">
|
|
1572
|
+
<button type="button" class="btn-primary" @click=${() => this.#P()}>
|
|
1573
|
+
Submit report
|
|
1574
|
+
</button>
|
|
1575
|
+
</div>
|
|
1576
|
+
</div>
|
|
1577
|
+
`;
|
|
1578
|
+
}
|
|
1579
|
+
#J() {
|
|
1580
|
+
const e = this._submit;
|
|
1581
|
+
if (e.stage === "auth") return this.#C({ id: null, type: "gate" });
|
|
1582
|
+
const t = {
|
|
1583
|
+
minting: "Preparing your uploads…",
|
|
1584
|
+
uploading: "Uploading your files…",
|
|
1585
|
+
"uploading-error": "One of your files did not upload.",
|
|
1586
|
+
requesting: "Sending your report…",
|
|
1587
|
+
"requesting-error": this._banner?.message || "We could not send your report."
|
|
1588
|
+
}[e.stage] || "Working…";
|
|
1589
|
+
return l`
|
|
1590
|
+
${h("Submitting your report.", t)}
|
|
1591
|
+
<div class="form-card">
|
|
1592
|
+
${e.files.map((s, i) => l`
|
|
1593
|
+
<div class="upload-row">
|
|
1594
|
+
<span class="name">${s.name}</span>
|
|
1595
|
+
<div class="upload-bar" role="progressbar"
|
|
1596
|
+
aria-valuemin="0" aria-valuemax="100"
|
|
1597
|
+
aria-valuenow=${Math.round((s.status === "done" ? 1 : s.progress) * 100)}>
|
|
1598
|
+
<div class="fill" style="width: ${(s.status === "done" ? 1 : s.progress) * 100}%"></div>
|
|
1599
|
+
</div>
|
|
1600
|
+
<span class="state ${s.status === "failed" ? "failed" : ""}">
|
|
1601
|
+
${s.status === "done" ? "Done" : s.status === "failed" ? "Failed" : "Uploading"}
|
|
1602
|
+
</span>
|
|
1603
|
+
${s.status === "failed" ? l`
|
|
1604
|
+
<button type="button" class="retry-chip" @click=${() => this.#M(i)}>
|
|
1605
|
+
Retry
|
|
1606
|
+
</button>
|
|
1607
|
+
` : c}
|
|
1608
|
+
</div>
|
|
1609
|
+
`)}
|
|
1610
|
+
${e.stage === "requesting-error" ? l`
|
|
1611
|
+
<div class="err" role="alert">${this._banner?.message || ""}</div>
|
|
1612
|
+
${this._banner?.retryAfter ? l`
|
|
1613
|
+
<div class="file-note">You can try again in about
|
|
1614
|
+
${Math.ceil(this._banner.retryAfter / 60)} minutes.</div>
|
|
1615
|
+
` : c}
|
|
1616
|
+
<button type="button" class="btn-primary" style="margin-top: 8px;"
|
|
1617
|
+
@click=${() => this.#L()}>Try again</button>
|
|
1618
|
+
` : c}
|
|
1619
|
+
${q(e).length && e.stage === "uploading-error" ? l`
|
|
1620
|
+
<div class="err" role="alert">Retry the failed
|
|
1621
|
+
${q(e).length === 1 ? "file" : "files"} to continue.</div>
|
|
1622
|
+
` : c}
|
|
1623
|
+
</div>
|
|
1624
|
+
`;
|
|
1625
|
+
}
|
|
1626
|
+
#X() {
|
|
1627
|
+
const e = U(this._form, this._steps), t = Ae(this._result, e), s = De({
|
|
1628
|
+
routing: this._result?.routing || {},
|
|
1629
|
+
flags: e,
|
|
1630
|
+
reference: this._result?.reference || ""
|
|
1631
|
+
});
|
|
1632
|
+
return l`
|
|
1633
|
+
<div class="confirm-card">
|
|
1634
|
+
<div class="c-head">
|
|
1635
|
+
<div class="c-check">${ue()}</div>
|
|
1636
|
+
<div>
|
|
1637
|
+
<div class="eyebrow">Report received</div>
|
|
1638
|
+
<h2>${t.title}</h2>
|
|
1639
|
+
</div>
|
|
1640
|
+
</div>
|
|
1641
|
+
<p>${t.body}</p>
|
|
1642
|
+
${t.urgentNote ? l`<p>${t.urgentNote}</p>` : c}
|
|
1643
|
+
<div class="refs">
|
|
1644
|
+
<span class="ref"><span class="rk">Reference</span>${this._result?.reference || ""}</span>
|
|
1645
|
+
</div>
|
|
1646
|
+
<button type="button" class="c-again" @click=${() => this.#w()}>
|
|
1647
|
+
Start a new report
|
|
1648
|
+
</button>
|
|
1649
|
+
</div>
|
|
1650
|
+
<div class="journey">
|
|
1651
|
+
<div class="j-eyebrow">Behind the scenes</div>
|
|
1652
|
+
<div class="j-title">What happens to your report now</div>
|
|
1653
|
+
${s.map((i, a) => l`
|
|
1654
|
+
<div class="j-step">
|
|
1655
|
+
<div class="j-dot">${a + 1}</div>
|
|
1656
|
+
<div class="j-body">
|
|
1657
|
+
<div class="j-t">${i.title}
|
|
1658
|
+
${i.pill ? l` <span class="flag-pill urgent">${i.pill}</span>` : c}
|
|
1659
|
+
</div>
|
|
1660
|
+
<div class="j-d">${i.body}</div>
|
|
1661
|
+
</div>
|
|
1662
|
+
</div>
|
|
1663
|
+
`)}
|
|
1664
|
+
</div>
|
|
1665
|
+
`;
|
|
1666
|
+
}
|
|
1667
|
+
#Z() {
|
|
1668
|
+
const e = this._phase === "submitting" || this._phase === "done" || this._phase === "loading" || this._phase === "load-error";
|
|
1669
|
+
return l`
|
|
1670
|
+
<div class="bottom-bar">
|
|
1671
|
+
<div class="inner">
|
|
1672
|
+
<button type="button" class="btn-ghost" ?disabled=${e || this._phase === "pick"}
|
|
1673
|
+
@click=${() => this.#D()}>← Back</button>
|
|
1674
|
+
<button type="button" class="btn-ghost" ?disabled=${this._phase === "submitting"}
|
|
1675
|
+
@click=${() => this.#w()}>Start over</button>
|
|
1676
|
+
<span class="note">Your report goes to a person on the right desk.</span>
|
|
1677
|
+
</div>
|
|
1678
|
+
</div>
|
|
1679
|
+
`;
|
|
1680
|
+
}
|
|
1681
|
+
updated() {
|
|
1682
|
+
this.renderRoot?.querySelector(".chat")?.lastElementChild?.scrollIntoView?.({ block: "nearest" });
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
class Ge extends I {
|
|
1686
|
+
static properties = {
|
|
1687
|
+
files: { attribute: !1 },
|
|
1688
|
+
lostMeta: { attribute: !1 },
|
|
1689
|
+
budget: { attribute: !1 },
|
|
1690
|
+
required: { type: Boolean },
|
|
1691
|
+
label: { type: String }
|
|
1692
|
+
};
|
|
1693
|
+
static styles = p`
|
|
1694
|
+
:host { display: block; }
|
|
1695
|
+
input[type=file] { font-family: inherit; font-size: 14px; }
|
|
1696
|
+
.file-list { margin: 8px 0 0; padding: 0; list-style: none; font-size: 13.5px; }
|
|
1697
|
+
.file-list li { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
|
|
1698
|
+
.file-list .size { color: var(--wral-semantic-fg-subtle, #64748B); font-size: 12px; }
|
|
1699
|
+
.remove {
|
|
1700
|
+
background: none; border: none; cursor: pointer; padding: 0 4px;
|
|
1701
|
+
color: var(--wral-base-red-on-light, #D1232A); font-size: 15px; line-height: 1;
|
|
1702
|
+
}
|
|
1703
|
+
.remove:focus-visible { outline: 2px solid var(--wral-base-secondary-1, #2594E3); outline-offset: 2px; }
|
|
1704
|
+
.lost {
|
|
1705
|
+
font-size: 13px; border-radius: 8px; padding: 10px 12px; margin-top: 8px;
|
|
1706
|
+
background: #FFF8D6;
|
|
1707
|
+
border: 1px solid var(--wral-base-yellow-3, #E5D416);
|
|
1708
|
+
color: var(--wral-base-gray-3, #35383C);
|
|
1709
|
+
}
|
|
1710
|
+
.err { color: var(--wral-base-red-on-light, #D1232A); font-size: 13px; margin-top: 6px; }
|
|
1711
|
+
`;
|
|
1712
|
+
constructor() {
|
|
1713
|
+
super(), this.files = [], this.lostMeta = [], this.budget = {}, this.required = !1, this.label = "Photos or video";
|
|
1714
|
+
}
|
|
1715
|
+
get #o() {
|
|
1716
|
+
return (this.budget?.allowedTypes || []).join(",");
|
|
1717
|
+
}
|
|
1718
|
+
#a(e) {
|
|
1719
|
+
this.dispatchEvent(new CustomEvent("files-changed", {
|
|
1720
|
+
detail: { files: e },
|
|
1721
|
+
bubbles: !0,
|
|
1722
|
+
composed: !0
|
|
1723
|
+
}));
|
|
1724
|
+
}
|
|
1725
|
+
#u(e) {
|
|
1726
|
+
const t = [...e.target.files || []];
|
|
1727
|
+
this.#a([...this.files || [], ...t]), e.target.value = "";
|
|
1728
|
+
}
|
|
1729
|
+
#t(e) {
|
|
1730
|
+
const t = [...this.files];
|
|
1731
|
+
t.splice(e, 1), this.#a(t);
|
|
1732
|
+
}
|
|
1733
|
+
render() {
|
|
1734
|
+
const e = this.files || [], { errors: t } = F(e, this.budget, { required: !1 });
|
|
1735
|
+
return l`
|
|
1736
|
+
<input type="file" multiple
|
|
1737
|
+
accept=${this.#o || c}
|
|
1738
|
+
aria-label=${this.label}
|
|
1739
|
+
@change=${this.#u}>
|
|
1740
|
+
${e.length ? l`
|
|
1741
|
+
<ul class="file-list">
|
|
1742
|
+
${e.map((s, i) => l`
|
|
1743
|
+
<li>
|
|
1744
|
+
<span>${s.name}</span>
|
|
1745
|
+
<span class="size">${Ve(s.size)}</span>
|
|
1746
|
+
<button type="button" class="remove"
|
|
1747
|
+
aria-label="Remove ${s.name}"
|
|
1748
|
+
@click=${() => this.#t(i)}>✕</button>
|
|
1749
|
+
</li>
|
|
1750
|
+
`)}
|
|
1751
|
+
</ul>
|
|
1752
|
+
` : c}
|
|
1753
|
+
${(this.lostMeta || []).length ? l`
|
|
1754
|
+
<div class="lost">
|
|
1755
|
+
Please re-attach your ${this.lostMeta.length === 1 ? "file" : "files"}
|
|
1756
|
+
(${this.lostMeta.map((s) => s.name).join(", ")}) —
|
|
1757
|
+
attachments can't be saved between visits.
|
|
1758
|
+
</div>
|
|
1759
|
+
` : c}
|
|
1760
|
+
${t.map((s) => l`<div class="err">${s.message}</div>`)}
|
|
1761
|
+
`;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
function Ve(r) {
|
|
1765
|
+
return Number.isFinite(r) ? r >= 1024 * 1024 ? `${(r / (1024 * 1024)).toFixed(1)} MB` : r >= 1024 ? `${Math.round(r / 1024)} KB` : `${r} B` : "";
|
|
1766
|
+
}
|
|
1767
|
+
const We = (r, e, t, s) => {
|
|
1768
|
+
const i = e.target.value.replace(/\D/g, "").slice(-1), a = [...t];
|
|
1769
|
+
a[r] = i;
|
|
1770
|
+
const n = a.join(""), o = a.every((d) => d !== "");
|
|
1771
|
+
return s("code-change", { value: n, complete: o }), i && o && s("code-complete", { value: n }), a;
|
|
1772
|
+
}, Ne = (r, e, t, s) => {
|
|
1773
|
+
if (e.key === "Backspace") {
|
|
1774
|
+
const i = [...t];
|
|
1775
|
+
!i[r] && r > 0 ? i[r - 1] = "" : i[r] && (i[r] = "");
|
|
1776
|
+
const a = i.join("");
|
|
1777
|
+
return s("code-change", { value: a, complete: !1 }), i;
|
|
1778
|
+
}
|
|
1779
|
+
return t;
|
|
1780
|
+
}, He = (r, e, t) => {
|
|
1781
|
+
r.preventDefault();
|
|
1782
|
+
const s = r.clipboardData.getData("text").replace(/\D/g, "").slice(0, 6);
|
|
1783
|
+
if (s.length === 0) return e;
|
|
1784
|
+
const i = [...e];
|
|
1785
|
+
for (let o = 0; o < s.length && o < 6; o++)
|
|
1786
|
+
i[o] = s[o];
|
|
1787
|
+
const a = i.join(""), n = i.every((o) => o !== "");
|
|
1788
|
+
return t("code-change", { value: a, complete: n }), s.length === 6 && t("code-complete", { value: a }), i;
|
|
1789
|
+
};
|
|
1790
|
+
class Je extends I {
|
|
1791
|
+
static formAssociated = !0;
|
|
1792
|
+
static properties = {
|
|
1793
|
+
_values: { type: Array, state: !0 },
|
|
1794
|
+
disabled: { type: Boolean, reflect: !0 }
|
|
1795
|
+
};
|
|
1796
|
+
static styles = p`
|
|
1797
|
+
:host {
|
|
1798
|
+
display: block;
|
|
1799
|
+
width: 100%;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
.code-inputs {
|
|
1803
|
+
display: flex;
|
|
1804
|
+
gap: 0.5rem;
|
|
1805
|
+
justify-content: space-between;
|
|
1806
|
+
width: 100%;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
.code-input {
|
|
1810
|
+
font-family: 'IBM Plex Sans Condensed', monospace;
|
|
1811
|
+
flex: 1;
|
|
1812
|
+
min-width: 0;
|
|
1813
|
+
aspect-ratio: 1;
|
|
1814
|
+
padding: 0;
|
|
1815
|
+
border: 2px solid #D7D4D2;
|
|
1816
|
+
border-radius: 0;
|
|
1817
|
+
font-size: 1.75rem;
|
|
1818
|
+
font-weight: 600;
|
|
1819
|
+
color: #001D68;
|
|
1820
|
+
background: white;
|
|
1821
|
+
text-align: center;
|
|
1822
|
+
transition: all 0.2s ease;
|
|
1823
|
+
box-sizing: border-box;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
.code-input:hover {
|
|
1827
|
+
border-color: #2594E3;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
.code-input:focus {
|
|
1831
|
+
outline: none;
|
|
1832
|
+
border-color: #2594E3;
|
|
1833
|
+
box-shadow: 0 0 0 4px rgba(37, 148, 227, 0.1);
|
|
1834
|
+
transform: scale(1.05);
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
.code-input:disabled {
|
|
1838
|
+
background: #EFECEA;
|
|
1839
|
+
color: #7C7C7C;
|
|
1840
|
+
cursor: not-allowed;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
@media (max-width: 640px) {
|
|
1844
|
+
.code-input {
|
|
1845
|
+
font-size: 1.25rem;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1850
|
+
.code-input:focus {
|
|
1851
|
+
transform: none;
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
`;
|
|
1855
|
+
constructor() {
|
|
1856
|
+
super(), this._internals = this.attachInternals(), this._values = ["", "", "", "", "", ""], this.disabled = !1;
|
|
1857
|
+
}
|
|
1858
|
+
// FACE API - value property
|
|
1859
|
+
get value() {
|
|
1860
|
+
return this._values.join("");
|
|
1861
|
+
}
|
|
1862
|
+
set value(e) {
|
|
1863
|
+
const t = String(e).replace(/\D/g, "").slice(0, 6);
|
|
1864
|
+
this._values = t.padEnd(6, "").split("").slice(0, 6), this._internals.setFormValue(t), this.requestUpdate();
|
|
1865
|
+
}
|
|
1866
|
+
formDisabledCallback(e) {
|
|
1867
|
+
this.disabled = e;
|
|
1868
|
+
}
|
|
1869
|
+
formResetCallback() {
|
|
1870
|
+
this.clear();
|
|
1871
|
+
}
|
|
1872
|
+
formStateRestoreCallback(e) {
|
|
1873
|
+
this.value = e;
|
|
1874
|
+
}
|
|
1875
|
+
dispatchCustomEvent(e, t) {
|
|
1876
|
+
this.dispatchEvent(new CustomEvent(e, {
|
|
1877
|
+
bubbles: !0,
|
|
1878
|
+
composed: !0,
|
|
1879
|
+
detail: t
|
|
1880
|
+
}));
|
|
1881
|
+
}
|
|
1882
|
+
clear() {
|
|
1883
|
+
this._values = ["", "", "", "", "", ""], this._internals.setFormValue(""), this.requestUpdate();
|
|
1884
|
+
}
|
|
1885
|
+
focus() {
|
|
1886
|
+
this.shadowRoot.querySelectorAll(".code-input")[0]?.focus();
|
|
1887
|
+
}
|
|
1888
|
+
getInputs() {
|
|
1889
|
+
return Array.from(this.shadowRoot.querySelectorAll(".code-input"));
|
|
1890
|
+
}
|
|
1891
|
+
focusInput(e) {
|
|
1892
|
+
this.getInputs()[e]?.focus();
|
|
1893
|
+
}
|
|
1894
|
+
inputHandler(e, t) {
|
|
1895
|
+
this._values = We(
|
|
1896
|
+
e,
|
|
1897
|
+
t,
|
|
1898
|
+
this._values,
|
|
1899
|
+
this.dispatchCustomEvent.bind(this)
|
|
1900
|
+
), t.target.value && e < 5 && this.focusInput(e + 1);
|
|
1901
|
+
}
|
|
1902
|
+
keydownHandler(e, t) {
|
|
1903
|
+
if (t.key === "Backspace") {
|
|
1904
|
+
const s = !this._values[e];
|
|
1905
|
+
this._values = Ne(
|
|
1906
|
+
e,
|
|
1907
|
+
t,
|
|
1908
|
+
this._values,
|
|
1909
|
+
this.dispatchCustomEvent.bind(this)
|
|
1910
|
+
), s && e > 0 && this.focusInput(e - 1);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
pasteHandler(e) {
|
|
1914
|
+
this._values = He(e, this._values, this.dispatchCustomEvent.bind(this));
|
|
1915
|
+
}
|
|
1916
|
+
render() {
|
|
1917
|
+
return l`
|
|
1918
|
+
<div class="code-inputs">
|
|
1919
|
+
${[0, 1, 2, 3, 4, 5].map((e) => l`
|
|
1920
|
+
<input
|
|
1921
|
+
type = "text"
|
|
1922
|
+
inputmode = "numeric"
|
|
1923
|
+
pattern = "[0-9]*"
|
|
1924
|
+
maxlength = "1"
|
|
1925
|
+
class = "code-input"
|
|
1926
|
+
autocomplete = ${e === 0 ? "one-time-code" : "off"}
|
|
1927
|
+
.value = ${this._values[e]}
|
|
1928
|
+
?disabled = ${this.disabled}
|
|
1929
|
+
@input = ${(...t) => this.inputHandler.bind(this)(e, ...t)}
|
|
1930
|
+
@keydown = ${(...t) => this.keydownHandler.bind(this)(e, ...t)}
|
|
1931
|
+
@paste = ${this.pasteHandler.bind(this)}
|
|
1932
|
+
/>
|
|
1933
|
+
`)}
|
|
1934
|
+
</div>
|
|
1935
|
+
`;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
export {
|
|
1939
|
+
Je as W,
|
|
1940
|
+
Ge as a,
|
|
1941
|
+
Qe as b,
|
|
1942
|
+
he as c,
|
|
1943
|
+
pe as d,
|
|
1944
|
+
fe as e
|
|
1945
|
+
};
|