@sbc-connect/nuxt-base 0.0.0-test-ld-20260520212549
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/.env.example +11 -0
- package/CHANGELOG.md +338 -0
- package/LICENSE +28 -0
- package/README.md +55 -0
- package/app/app.config.ts +441 -0
- package/app/app.vue +18 -0
- package/app/assets/css/connect-base-layout.css +13 -0
- package/app/assets/css/connect-base-tw.css +303 -0
- package/app/components/Connect/Address/Display.vue +84 -0
- package/app/components/Connect/BCGovLogo.vue +42 -0
- package/app/components/Connect/Breadcrumb.vue +68 -0
- package/app/components/Connect/ButtonControl/Buttons.vue +28 -0
- package/app/components/Connect/ButtonControl/Index.vue +66 -0
- package/app/components/Connect/ButtonControl/Wrapper.vue +18 -0
- package/app/components/Connect/Contact/Display.vue +65 -0
- package/app/components/Connect/FloatingLabel.vue +67 -0
- package/app/components/Connect/Footer.vue +99 -0
- package/app/components/Connect/Header/LogoHomeLink.vue +20 -0
- package/app/components/Connect/Header/WhatsNew.vue +36 -0
- package/app/components/Connect/Header/Wrapper.vue +14 -0
- package/app/components/Connect/Header/index.vue +17 -0
- package/app/components/Connect/I18n/Helper.vue +40 -0
- package/app/components/Connect/Input.vue +69 -0
- package/app/components/Connect/InputDate.vue +104 -0
- package/app/components/Connect/Layout/Index.vue +25 -0
- package/app/components/Connect/Layout/Sidebar.vue +38 -0
- package/app/components/Connect/LocaleSelect.vue +37 -0
- package/app/components/Connect/Modal/Error.vue +76 -0
- package/app/components/Connect/Modal/index.vue +51 -0
- package/app/components/Connect/PageSection.vue +57 -0
- package/app/components/Connect/Select.vue +28 -0
- package/app/components/Connect/Slideover/WhatsNew.vue +61 -0
- package/app/components/Connect/Spinner.vue +91 -0
- package/app/components/Connect/SystemBanner.vue +53 -0
- package/app/components/Connect/Textarea.vue +27 -0
- package/app/components/Connect/Tombstone/Item.vue +51 -0
- package/app/components/Connect/Tombstone/index.vue +123 -0
- package/app/components/Connect/Transition/Collapse.vue +39 -0
- package/app/components/Connect/Transition/Fade.vue +31 -0
- package/app/components/HelloWorld/Index.vue +14 -0
- package/app/composables/useConnectButtonControl.ts +146 -0
- package/app/composables/useConnectLaunchDarkly.ts +173 -0
- package/app/composables/useConnectModal.ts +13 -0
- package/app/composables/useConnectTombstone.ts +25 -0
- package/app/coverage/app/app.config.ts.html +1408 -0
- package/app/coverage/app/app.vue.html +139 -0
- package/app/coverage/app/components/Connect/Address/Display.vue.html +337 -0
- package/app/coverage/app/components/Connect/Address/index.html +116 -0
- package/app/coverage/app/components/Connect/BCGovLogo.vue.html +211 -0
- package/app/coverage/app/components/Connect/Breadcrumb.vue.html +289 -0
- package/app/coverage/app/components/Connect/ButtonControl/Buttons.vue.html +166 -0
- package/app/coverage/app/components/Connect/ButtonControl/Index.vue.html +283 -0
- package/app/coverage/app/components/Connect/ButtonControl/Wrapper.vue.html +139 -0
- package/app/coverage/app/components/Connect/ButtonControl/index.html +146 -0
- package/app/coverage/app/components/Connect/Contact/Display.vue.html +280 -0
- package/app/coverage/app/components/Connect/Contact/index.html +116 -0
- package/app/coverage/app/components/Connect/FloatingLabel.vue.html +286 -0
- package/app/coverage/app/components/Connect/Footer.vue.html +382 -0
- package/app/coverage/app/components/Connect/Header/LogoHomeLink.vue.html +145 -0
- package/app/coverage/app/components/Connect/Header/WhatsNew.vue.html +193 -0
- package/app/coverage/app/components/Connect/Header/Wrapper.vue.html +127 -0
- package/app/coverage/app/components/Connect/Header/index.html +161 -0
- package/app/coverage/app/components/Connect/Header/index.vue.html +136 -0
- package/app/coverage/app/components/Connect/I18n/Helper.vue.html +205 -0
- package/app/coverage/app/components/Connect/I18n/index.html +116 -0
- package/app/coverage/app/components/Connect/Input.vue.html +262 -0
- package/app/coverage/app/components/Connect/InputDate.vue.html +406 -0
- package/app/coverage/app/components/Connect/Layout/Index.vue.html +160 -0
- package/app/coverage/app/components/Connect/Layout/Sidebar.vue.html +199 -0
- package/app/coverage/app/components/Connect/Layout/index.html +131 -0
- package/app/coverage/app/components/Connect/LocaleSelect.vue.html +196 -0
- package/app/coverage/app/components/Connect/Modal/Error.vue.html +313 -0
- package/app/coverage/app/components/Connect/Modal/index.html +131 -0
- package/app/coverage/app/components/Connect/Modal/index.vue.html +238 -0
- package/app/coverage/app/components/Connect/PageSection.vue.html +256 -0
- package/app/coverage/app/components/Connect/Select.vue.html +169 -0
- package/app/coverage/app/components/Connect/Slideover/WhatsNew.vue.html +268 -0
- package/app/coverage/app/components/Connect/Slideover/index.html +116 -0
- package/app/coverage/app/components/Connect/Spinner.vue.html +358 -0
- package/app/coverage/app/components/Connect/SystemBanner.vue.html +244 -0
- package/app/coverage/app/components/Connect/Textarea.vue.html +160 -0
- package/app/coverage/app/components/Connect/Tombstone/Item.vue.html +238 -0
- package/app/coverage/app/components/Connect/Tombstone/index.html +131 -0
- package/app/coverage/app/components/Connect/Tombstone/index.vue.html +454 -0
- package/app/coverage/app/components/Connect/Transition/Collapse.vue.html +202 -0
- package/app/coverage/app/components/Connect/Transition/Fade.vue.html +178 -0
- package/app/coverage/app/components/Connect/Transition/index.html +131 -0
- package/app/coverage/app/components/Connect/index.html +281 -0
- package/app/coverage/app/components/HelloWorld/Index.vue.html +127 -0
- package/app/coverage/app/components/HelloWorld/index.html +116 -0
- package/app/coverage/app/composables/index.html +161 -0
- package/app/coverage/app/composables/useConnectButtonControl.ts.html +523 -0
- package/app/coverage/app/composables/useConnectLaunchDarkly.ts.html +595 -0
- package/app/coverage/app/composables/useConnectModal.ts.html +124 -0
- package/app/coverage/app/composables/useConnectTombstone.ts.html +160 -0
- package/app/coverage/app/error.vue.html +247 -0
- package/app/coverage/app/index.html +146 -0
- package/app/coverage/app/interfaces/connect-address.ts.html +121 -0
- package/app/coverage/app/interfaces/connect-breadcrumb.ts.html +100 -0
- package/app/coverage/app/interfaces/connect-button-control.ts.html +151 -0
- package/app/coverage/app/interfaces/connect-contact-item.ts.html +103 -0
- package/app/coverage/app/interfaces/connect-modal.ts.html +100 -0
- package/app/coverage/app/interfaces/connect-tombstone.ts.html +208 -0
- package/app/coverage/app/interfaces/connect-whatsnew.ts.html +127 -0
- package/app/coverage/app/interfaces/index.html +206 -0
- package/app/coverage/app/layouts/ConnectBase.vue.html +100 -0
- package/app/coverage/app/layouts/index.html +116 -0
- package/app/coverage/app/plugins/index.html +146 -0
- package/app/coverage/app/plugins/maska.client.ts.html +115 -0
- package/app/coverage/app/plugins/sanitize.client.ts.html +151 -0
- package/app/coverage/app/plugins/whats-new.client.ts.html +220 -0
- package/app/coverage/app/types/dates.ts.html +136 -0
- package/app/coverage/app/types/index.html +116 -0
- package/app/coverage/app/utils/appendUrlParam.ts.html +97 -0
- package/app/coverage/app/utils/getErrorMessage.ts.html +211 -0
- package/app/coverage/app/utils/getErrorStatus.ts.html +196 -0
- package/app/coverage/app/utils/index.html +191 -0
- package/app/coverage/app/utils/logFetchError.ts.html +193 -0
- package/app/coverage/app/utils/parseSpecialCharacters.ts.html +160 -0
- package/app/coverage/app/utils/setBreadcrumbs.ts.html +100 -0
- package/app/coverage/base.css +224 -0
- package/app/coverage/block-navigation.js +87 -0
- package/app/coverage/clover.xml +2094 -0
- package/app/coverage/coverage-final.json +58 -0
- package/app/coverage/favicon.png +0 -0
- package/app/coverage/index.html +386 -0
- package/app/coverage/prettify.css +1 -0
- package/app/coverage/prettify.js +2 -0
- package/app/coverage/sort-arrow-sprite.png +0 -0
- package/app/coverage/sorter.js +210 -0
- package/app/error.vue +54 -0
- package/app/interfaces/connect-address.ts +12 -0
- package/app/interfaces/connect-breadcrumb.ts +5 -0
- package/app/interfaces/connect-button-control.ts +23 -0
- package/app/interfaces/connect-contact-item.ts +6 -0
- package/app/interfaces/connect-modal.ts +5 -0
- package/app/interfaces/connect-tombstone.ts +41 -0
- package/app/interfaces/connect-whatsnew.ts +14 -0
- package/app/layouts/ConnectBase.vue +5 -0
- package/app/plugins/maska.client.ts +10 -0
- package/app/plugins/sanitize.client.ts +22 -0
- package/app/plugins/whats-new.client.ts +45 -0
- package/app/spa-loading-template.html +61 -0
- package/app/types/base-app-config.d.ts +15 -0
- package/app/types/base-directives.d.ts +7 -0
- package/app/types/base-page-meta.d.ts +8 -0
- package/app/types/dates.ts +17 -0
- package/app/utils/appendUrlParam.ts +4 -0
- package/app/utils/getErrorMessage.ts +42 -0
- package/app/utils/getErrorStatus.ts +37 -0
- package/app/utils/logFetchError.ts +36 -0
- package/app/utils/parseSpecialCharacters.ts +25 -0
- package/app/utils/setBreadcrumbs.ts +7 -0
- package/firebase.json +34 -0
- package/i18n/locales/en-CA.ts +70 -0
- package/i18n/locales/fr-CA.ts +96 -0
- package/nuxt.config.ts +147 -0
- package/package.json +49 -0
- package/public/BCGovLogo/gov_bc_logo_horiz_en.png +0 -0
- package/public/BCGovLogo/gov_bc_logo_horiz_fr.png +0 -0
- package/public/BCGovLogo/gov_bc_logo_vert_en.png +0 -0
- package/public/BCGovLogo/gov_bc_logo_vert_fr.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/fonts/BCSans/BCSans-Bold.woff +0 -0
- package/public/fonts/BCSans/BCSans-Bold.woff2 +0 -0
- package/public/fonts/BCSans/BCSans-BoldItalic.woff +0 -0
- package/public/fonts/BCSans/BCSans-BoldItalic.woff2 +0 -0
- package/public/fonts/BCSans/BCSans-Italic.woff +0 -0
- package/public/fonts/BCSans/BCSans-Italic.woff2 +0 -0
- package/public/fonts/BCSans/BCSans-Regular.woff +0 -0
- package/public/fonts/BCSans/BCSans-Regular.woff2 +0 -0
- package/public/fonts/BCSans/LICENSE_OFL.txt +94 -0
- package/test-results/.last-run.json +7 -0
- package/test-results/error-page-404-Error-vue-D-1eb51-cted-visuals-with-404-error-Desktop-Chrome/error-context.md +135 -0
- package/test-results/error-page-404-Error-vue-D-1eb51-cted-visuals-with-404-error-Desktop-Chrome/test-failed-1.png +0 -0
- package/test-results/error-page-404-Error-vue-D-1eb51-cted-visuals-with-404-error-Desktop-Chrome/trace.zip +0 -0
- package/test-results/error-page-500-Error-vue-D-8c2cf-cted-visuals-with-500-error-Desktop-Chrome/error-context.md +135 -0
- package/test-results/error-page-500-Error-vue-D-8c2cf-cted-visuals-with-500-error-Desktop-Chrome/test-failed-1.png +0 -0
- package/test-results/error-page-500-Error-vue-D-8c2cf-cted-visuals-with-500-error-Desktop-Chrome/trace.zip +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
var addSorting = (function() {
|
|
3
|
+
'use strict';
|
|
4
|
+
var cols,
|
|
5
|
+
currentSort = {
|
|
6
|
+
index: 0,
|
|
7
|
+
desc: false
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// returns the summary table element
|
|
11
|
+
function getTable() {
|
|
12
|
+
return document.querySelector('.coverage-summary');
|
|
13
|
+
}
|
|
14
|
+
// returns the thead element of the summary table
|
|
15
|
+
function getTableHeader() {
|
|
16
|
+
return getTable().querySelector('thead tr');
|
|
17
|
+
}
|
|
18
|
+
// returns the tbody element of the summary table
|
|
19
|
+
function getTableBody() {
|
|
20
|
+
return getTable().querySelector('tbody');
|
|
21
|
+
}
|
|
22
|
+
// returns the th element for nth column
|
|
23
|
+
function getNthColumn(n) {
|
|
24
|
+
return getTableHeader().querySelectorAll('th')[n];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function onFilterInput() {
|
|
28
|
+
const searchValue = document.getElementById('fileSearch').value;
|
|
29
|
+
const rows = document.getElementsByTagName('tbody')[0].children;
|
|
30
|
+
|
|
31
|
+
// Try to create a RegExp from the searchValue. If it fails (invalid regex),
|
|
32
|
+
// it will be treated as a plain text search
|
|
33
|
+
let searchRegex;
|
|
34
|
+
try {
|
|
35
|
+
searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive
|
|
36
|
+
} catch (error) {
|
|
37
|
+
searchRegex = null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < rows.length; i++) {
|
|
41
|
+
const row = rows[i];
|
|
42
|
+
let isMatch = false;
|
|
43
|
+
|
|
44
|
+
if (searchRegex) {
|
|
45
|
+
// If a valid regex was created, use it for matching
|
|
46
|
+
isMatch = searchRegex.test(row.textContent);
|
|
47
|
+
} else {
|
|
48
|
+
// Otherwise, fall back to the original plain text search
|
|
49
|
+
isMatch = row.textContent
|
|
50
|
+
.toLowerCase()
|
|
51
|
+
.includes(searchValue.toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
row.style.display = isMatch ? '' : 'none';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// loads the search box
|
|
59
|
+
function addSearchBox() {
|
|
60
|
+
var template = document.getElementById('filterTemplate');
|
|
61
|
+
var templateClone = template.content.cloneNode(true);
|
|
62
|
+
templateClone.getElementById('fileSearch').oninput = onFilterInput;
|
|
63
|
+
template.parentElement.appendChild(templateClone);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// loads all columns
|
|
67
|
+
function loadColumns() {
|
|
68
|
+
var colNodes = getTableHeader().querySelectorAll('th'),
|
|
69
|
+
colNode,
|
|
70
|
+
cols = [],
|
|
71
|
+
col,
|
|
72
|
+
i;
|
|
73
|
+
|
|
74
|
+
for (i = 0; i < colNodes.length; i += 1) {
|
|
75
|
+
colNode = colNodes[i];
|
|
76
|
+
col = {
|
|
77
|
+
key: colNode.getAttribute('data-col'),
|
|
78
|
+
sortable: !colNode.getAttribute('data-nosort'),
|
|
79
|
+
type: colNode.getAttribute('data-type') || 'string'
|
|
80
|
+
};
|
|
81
|
+
cols.push(col);
|
|
82
|
+
if (col.sortable) {
|
|
83
|
+
col.defaultDescSort = col.type === 'number';
|
|
84
|
+
colNode.innerHTML =
|
|
85
|
+
colNode.innerHTML + '<span class="sorter"></span>';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return cols;
|
|
89
|
+
}
|
|
90
|
+
// attaches a data attribute to every tr element with an object
|
|
91
|
+
// of data values keyed by column name
|
|
92
|
+
function loadRowData(tableRow) {
|
|
93
|
+
var tableCols = tableRow.querySelectorAll('td'),
|
|
94
|
+
colNode,
|
|
95
|
+
col,
|
|
96
|
+
data = {},
|
|
97
|
+
i,
|
|
98
|
+
val;
|
|
99
|
+
for (i = 0; i < tableCols.length; i += 1) {
|
|
100
|
+
colNode = tableCols[i];
|
|
101
|
+
col = cols[i];
|
|
102
|
+
val = colNode.getAttribute('data-value');
|
|
103
|
+
if (col.type === 'number') {
|
|
104
|
+
val = Number(val);
|
|
105
|
+
}
|
|
106
|
+
data[col.key] = val;
|
|
107
|
+
}
|
|
108
|
+
return data;
|
|
109
|
+
}
|
|
110
|
+
// loads all row data
|
|
111
|
+
function loadData() {
|
|
112
|
+
var rows = getTableBody().querySelectorAll('tr'),
|
|
113
|
+
i;
|
|
114
|
+
|
|
115
|
+
for (i = 0; i < rows.length; i += 1) {
|
|
116
|
+
rows[i].data = loadRowData(rows[i]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// sorts the table using the data for the ith column
|
|
120
|
+
function sortByIndex(index, desc) {
|
|
121
|
+
var key = cols[index].key,
|
|
122
|
+
sorter = function(a, b) {
|
|
123
|
+
a = a.data[key];
|
|
124
|
+
b = b.data[key];
|
|
125
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
126
|
+
},
|
|
127
|
+
finalSorter = sorter,
|
|
128
|
+
tableBody = document.querySelector('.coverage-summary tbody'),
|
|
129
|
+
rowNodes = tableBody.querySelectorAll('tr'),
|
|
130
|
+
rows = [],
|
|
131
|
+
i;
|
|
132
|
+
|
|
133
|
+
if (desc) {
|
|
134
|
+
finalSorter = function(a, b) {
|
|
135
|
+
return -1 * sorter(a, b);
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (i = 0; i < rowNodes.length; i += 1) {
|
|
140
|
+
rows.push(rowNodes[i]);
|
|
141
|
+
tableBody.removeChild(rowNodes[i]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
rows.sort(finalSorter);
|
|
145
|
+
|
|
146
|
+
for (i = 0; i < rows.length; i += 1) {
|
|
147
|
+
tableBody.appendChild(rows[i]);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// removes sort indicators for current column being sorted
|
|
151
|
+
function removeSortIndicators() {
|
|
152
|
+
var col = getNthColumn(currentSort.index),
|
|
153
|
+
cls = col.className;
|
|
154
|
+
|
|
155
|
+
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
|
|
156
|
+
col.className = cls;
|
|
157
|
+
}
|
|
158
|
+
// adds sort indicators for current column being sorted
|
|
159
|
+
function addSortIndicators() {
|
|
160
|
+
getNthColumn(currentSort.index).className += currentSort.desc
|
|
161
|
+
? ' sorted-desc'
|
|
162
|
+
: ' sorted';
|
|
163
|
+
}
|
|
164
|
+
// adds event listeners for all sorter widgets
|
|
165
|
+
function enableUI() {
|
|
166
|
+
var i,
|
|
167
|
+
el,
|
|
168
|
+
ithSorter = function ithSorter(i) {
|
|
169
|
+
var col = cols[i];
|
|
170
|
+
|
|
171
|
+
return function() {
|
|
172
|
+
var desc = col.defaultDescSort;
|
|
173
|
+
|
|
174
|
+
if (currentSort.index === i) {
|
|
175
|
+
desc = !currentSort.desc;
|
|
176
|
+
}
|
|
177
|
+
sortByIndex(i, desc);
|
|
178
|
+
removeSortIndicators();
|
|
179
|
+
currentSort.index = i;
|
|
180
|
+
currentSort.desc = desc;
|
|
181
|
+
addSortIndicators();
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
for (i = 0; i < cols.length; i += 1) {
|
|
185
|
+
if (cols[i].sortable) {
|
|
186
|
+
// add the click event handler on the th so users
|
|
187
|
+
// dont have to click on those tiny arrows
|
|
188
|
+
el = getNthColumn(i).querySelector('.sorter').parentElement;
|
|
189
|
+
if (el.addEventListener) {
|
|
190
|
+
el.addEventListener('click', ithSorter(i));
|
|
191
|
+
} else {
|
|
192
|
+
el.attachEvent('onclick', ithSorter(i));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// adds sorting functionality to the UI
|
|
198
|
+
return function() {
|
|
199
|
+
if (!getTable()) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
cols = loadColumns();
|
|
203
|
+
loadData();
|
|
204
|
+
addSearchBox();
|
|
205
|
+
addSortIndicators();
|
|
206
|
+
enableUI();
|
|
207
|
+
};
|
|
208
|
+
})();
|
|
209
|
+
|
|
210
|
+
window.addEventListener('load', addSorting);
|
package/app/error.vue
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/* eslint-disable vue/define-props-declaration */
|
|
3
|
+
import type { NuxtError } from '#app'
|
|
4
|
+
|
|
5
|
+
const { t } = useI18n()
|
|
6
|
+
const localePath = useLocalePath()
|
|
7
|
+
|
|
8
|
+
const props = defineProps({
|
|
9
|
+
error: { type: Object as () => NuxtError, default: undefined }
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
// TODO: update with other error codes?
|
|
13
|
+
const errorKey: string | number = props.error?.statusCode === 404 ? 404 : 'unknown'
|
|
14
|
+
|
|
15
|
+
// cant use definePageMeta in error.vue
|
|
16
|
+
useRoute().meta.hideBreadcrumbs = true
|
|
17
|
+
|
|
18
|
+
useHead({
|
|
19
|
+
title: errorKey === 404 ? t('connect.page.error.404.title') : t('connect.page.error.unknown.title')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const errorObj = {
|
|
23
|
+
name: props.error?.name || '',
|
|
24
|
+
cause: props.error?.cause || '',
|
|
25
|
+
message: props.error?.message || '',
|
|
26
|
+
statusCode: props.error?.statusCode || '',
|
|
27
|
+
statusMessage: props.error?.statusMessage || '',
|
|
28
|
+
stack: props.error?.stack || '',
|
|
29
|
+
data: props.error?.data || ''
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
onMounted(() => {
|
|
33
|
+
console.error('Application Error: ', errorObj)
|
|
34
|
+
})
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<UApp :toaster="{ position: 'bottom-center' }">
|
|
39
|
+
<NuxtLayout name="connect-base">
|
|
40
|
+
<div class="m-auto flex flex-col items-center gap-4">
|
|
41
|
+
<h1>
|
|
42
|
+
{{ $t(`connect.page.error.${errorKey}.h1`) }}
|
|
43
|
+
</h1>
|
|
44
|
+
<p>{{ $t(`connect.page.error.${errorKey}.content`) }}</p>
|
|
45
|
+
<UButton
|
|
46
|
+
:label="$t('connect.label.goHome')"
|
|
47
|
+
icon="i-mdi-home"
|
|
48
|
+
size="xl"
|
|
49
|
+
:to="localePath('/')"
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
</NuxtLayout>
|
|
53
|
+
</UApp>
|
|
54
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ButtonProps } from '@nuxt/ui'
|
|
2
|
+
|
|
3
|
+
export interface ConnectButton extends ButtonProps {
|
|
4
|
+
alertText?: string
|
|
5
|
+
removeAlertSpacing?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ConnectButtonGroup {
|
|
9
|
+
alertText?: string
|
|
10
|
+
buttons: ConnectButton[]
|
|
11
|
+
class?: string
|
|
12
|
+
stacked?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ConnectButtonControl {
|
|
16
|
+
leftGroup: ConnectButtonGroup
|
|
17
|
+
rightGroup: ConnectButtonGroup
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ConnectButtonControlOptions {
|
|
21
|
+
handleLoading: boolean
|
|
22
|
+
clearAlertTextOnClick: boolean
|
|
23
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ButtonProps, IconProps, BadgeProps, LinkProps } from '@nuxt/ui'
|
|
2
|
+
import type { VNode } from 'vue'
|
|
3
|
+
|
|
4
|
+
export interface ConnectTombstoneSideDetail {
|
|
5
|
+
label: string
|
|
6
|
+
value: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ConnectTombstoneTitle {
|
|
10
|
+
text: string
|
|
11
|
+
as: 'span' | 'p' | 'div' | 'h1' | 'h2' | 'h3' | 'h4'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// must also update ConnectTombstoneItem props if updating this type
|
|
15
|
+
export interface ConnectTombstoneLink extends LinkProps {
|
|
16
|
+
label?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// must also update ConnectTombstoneItem props if updating this type
|
|
20
|
+
export interface ConnectTombstoneItem {
|
|
21
|
+
text?: string
|
|
22
|
+
itemClass?: string
|
|
23
|
+
icon?: IconProps
|
|
24
|
+
badge?: BadgeProps
|
|
25
|
+
link?: ConnectTombstoneLink
|
|
26
|
+
vNode?: VNode
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ConnectTombstoneBtn = {
|
|
30
|
+
button?: ButtonProps
|
|
31
|
+
vNode?: VNode
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ConnectTombstoneState {
|
|
35
|
+
loading: boolean
|
|
36
|
+
title: ConnectTombstoneTitle
|
|
37
|
+
subtitles: ConnectTombstoneItem[]
|
|
38
|
+
details: ConnectTombstoneItem[]
|
|
39
|
+
sideDetails: ConnectTombstoneSideDetail[]
|
|
40
|
+
bottomButtons: ConnectTombstoneBtn[]
|
|
41
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ConnectWhatsNewItem {
|
|
2
|
+
app: string
|
|
3
|
+
date: string
|
|
4
|
+
description: string
|
|
5
|
+
id: number
|
|
6
|
+
priority: boolean
|
|
7
|
+
read: boolean
|
|
8
|
+
title: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ConnectWhatsNewState {
|
|
12
|
+
viewed: boolean
|
|
13
|
+
items: ConnectWhatsNewItem[]
|
|
14
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import DOMPurify from 'dompurify'
|
|
2
|
+
|
|
3
|
+
export default defineNuxtPlugin({
|
|
4
|
+
name: 'connect-sanitize-plugin',
|
|
5
|
+
parallel: true,
|
|
6
|
+
setup(nuxtApp) {
|
|
7
|
+
nuxtApp.vueApp.directive('sanitize', {
|
|
8
|
+
beforeMount(el, binding) {
|
|
9
|
+
el.innerHTML = DOMPurify.sanitize(binding.value)
|
|
10
|
+
},
|
|
11
|
+
updated(el, binding) {
|
|
12
|
+
el.innerHTML = DOMPurify.sanitize(binding.value)
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
provide: {
|
|
18
|
+
sanitize: DOMPurify.sanitize
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useStorage } from '@vueuse/core'
|
|
2
|
+
import { isEqual } from 'es-toolkit'
|
|
3
|
+
|
|
4
|
+
export default defineNuxtPlugin({
|
|
5
|
+
name: 'connect-whats-new-plugin',
|
|
6
|
+
parallel: true,
|
|
7
|
+
setup(nuxtApp) {
|
|
8
|
+
// exit early if whats new === false
|
|
9
|
+
const whatsNew = useAppConfig().connect.header.whatsNew
|
|
10
|
+
if (!whatsNew) {
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
const rtc = useRuntimeConfig().public
|
|
14
|
+
const url = rtc.statusApiUrl + rtc.statusApiVersion
|
|
15
|
+
|
|
16
|
+
// load whats new only once when app mounts
|
|
17
|
+
nuxtApp.hook('app:mounted', async () => {
|
|
18
|
+
// set default localStorage state
|
|
19
|
+
const state = useStorage<ConnectWhatsNewState>(
|
|
20
|
+
'connect-whats-new', // storage key
|
|
21
|
+
{ viewed: false, items: [] }, // default value
|
|
22
|
+
localStorage, // could change this to sessionStorage
|
|
23
|
+
{ mergeDefaults: true } // merge anything already in localStorage with default value
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// fetch items
|
|
28
|
+
const res = await $fetch<ConnectWhatsNewItem[]>(`${url}/whatsnew`, {
|
|
29
|
+
parseResponse: JSON.parse, // required to parse JSON correctly
|
|
30
|
+
headers: {
|
|
31
|
+
'App-Name': rtc.appName
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// only update value if new items exist
|
|
36
|
+
if (!isEqual(state.value.items, res) && res.length > 0) {
|
|
37
|
+
state.value.items = res
|
|
38
|
+
state.value.viewed = false
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// silently handle errors
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<head>
|
|
2
|
+
<title>Loading...</title>
|
|
3
|
+
<style>
|
|
4
|
+
.loading-container {
|
|
5
|
+
position: fixed;
|
|
6
|
+
inset: 0;
|
|
7
|
+
z-index: 9999;
|
|
8
|
+
background-color: rgba(241, 243, 245, 0.8);
|
|
9
|
+
display: flex;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
align-items: center;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.loader {
|
|
15
|
+
display: block;
|
|
16
|
+
width: 75px;
|
|
17
|
+
height: 75px;
|
|
18
|
+
animation: rotate 1.5s linear infinite;
|
|
19
|
+
transform-origin: 50% 50%;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.loader .path {
|
|
23
|
+
stroke: #1669bb;
|
|
24
|
+
stroke-linecap: round;
|
|
25
|
+
animation: dash 1.25s ease-in-out infinite;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@keyframes rotate {
|
|
29
|
+
100% {
|
|
30
|
+
transform: rotate(360deg);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@keyframes dash {
|
|
35
|
+
0% {
|
|
36
|
+
stroke-dasharray: 1, 150;
|
|
37
|
+
stroke-dashoffset: 0;
|
|
38
|
+
}
|
|
39
|
+
50% {
|
|
40
|
+
stroke-dasharray: 90, 150;
|
|
41
|
+
stroke-dashoffset: -35;
|
|
42
|
+
}
|
|
43
|
+
100% {
|
|
44
|
+
stroke-dasharray: 90, 150;
|
|
45
|
+
stroke-dashoffset: -124;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
</style>
|
|
49
|
+
</head>
|
|
50
|
+
<div class="loading-container">
|
|
51
|
+
<svg class="loader" viewBox="0 0 50 50">
|
|
52
|
+
<circle
|
|
53
|
+
class="path"
|
|
54
|
+
cx="25"
|
|
55
|
+
cy="25"
|
|
56
|
+
r="20"
|
|
57
|
+
fill="none"
|
|
58
|
+
stroke-width="5"
|
|
59
|
+
/>
|
|
60
|
+
</svg>
|
|
61
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISO date string (YYYY-MM-DD), assumed to be in Pacific timezone.
|
|
3
|
+
* @example "2021-12-31"
|
|
4
|
+
*/
|
|
5
|
+
export type IsoDatePacific = string
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* API date-time string (YYYY-MM-DDTHH:MM:SS[.MMMMMM]+00.00), assumed to be in UTC.
|
|
9
|
+
* @example "2022-05-09T16:26:14.839032+00:00"
|
|
10
|
+
*/
|
|
11
|
+
export type ApiDateTimeUtc = string
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Formatted date-time string with explicit GMT timezone (ie, UTC).
|
|
15
|
+
* @example "Tue, 19 Apr 2022 16:40:18 GMT"
|
|
16
|
+
*/
|
|
17
|
+
export type FormattedDateTimeGmt = string
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Safely extracts an error message from an unknown error object.
|
|
5
|
+
*
|
|
6
|
+
* The search order (Highest Priority to Lowest) is:
|
|
7
|
+
* 1. Current object's 'message' (must be a non-empty string).
|
|
8
|
+
* 2. Current object's 'detail' (must be a non-empty string).
|
|
9
|
+
* 3. Recursively searches the 'error.data.rootCause' object.
|
|
10
|
+
* 4. Recursively searches the 'error.data' object.
|
|
11
|
+
*
|
|
12
|
+
* This function will return the first valid, non-empty string message found.
|
|
13
|
+
*
|
|
14
|
+
* @param error - The error object (unknown).
|
|
15
|
+
* @returns The error message if found, else 'undefined'.
|
|
16
|
+
*/
|
|
17
|
+
export function getErrorMessage(error: unknown): string | undefined {
|
|
18
|
+
if (error && typeof error === 'object') {
|
|
19
|
+
const errorObject = error as Record<string, any>
|
|
20
|
+
|
|
21
|
+
const message = errorObject.message
|
|
22
|
+
if (message && typeof message === 'string' && message.length > 0) {
|
|
23
|
+
return message
|
|
24
|
+
}
|
|
25
|
+
const detail = errorObject.detail
|
|
26
|
+
if (detail && typeof detail === 'string' && detail.length > 0) {
|
|
27
|
+
return detail
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const rootCauseMessage = getErrorMessage(errorObject?.data?.rootCause)
|
|
31
|
+
if (rootCauseMessage) {
|
|
32
|
+
return rootCauseMessage
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const dataMessage = getErrorMessage(errorObject?.data)
|
|
36
|
+
if (dataMessage) {
|
|
37
|
+
return dataMessage
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return undefined
|
|
42
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safely extracts the HTTP status code from a thrown error.
|
|
3
|
+
*
|
|
4
|
+
* This provides a reliable way to get a status code from an `unknown` error type.
|
|
5
|
+
*
|
|
6
|
+
* @param error - The error object to inspect. It can be of any type.
|
|
7
|
+
* @returns The numeric HTTP status code if found, otherwise `undefined`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* getErrorStatus({ response: { status: 404 } }); // returns 404
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* getErrorStatus({ statusCode: 403 }); // returns 403
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* getErrorStatus(new Error("Network failed")); // returns undefined
|
|
17
|
+
*/
|
|
18
|
+
export function getErrorStatus(error: unknown): number | undefined {
|
|
19
|
+
// check if error is valid object
|
|
20
|
+
if (
|
|
21
|
+
error
|
|
22
|
+
&& typeof error === 'object'
|
|
23
|
+
&& ('statusCode' in error || 'response' in error)
|
|
24
|
+
) {
|
|
25
|
+
// handle case where error.response.status
|
|
26
|
+
if ('response' in error && error.response && typeof error.response === 'object' && 'status' in error.response) {
|
|
27
|
+
return error.response.status as number
|
|
28
|
+
}
|
|
29
|
+
// handle case where no error.response.status, fallback to error.statusCode
|
|
30
|
+
if ('statusCode' in error) {
|
|
31
|
+
return error.statusCode as number
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// return undefined if no status or statusCode
|
|
36
|
+
return undefined
|
|
37
|
+
}
|