@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.
Files changed (179) hide show
  1. package/.env.example +11 -0
  2. package/CHANGELOG.md +338 -0
  3. package/LICENSE +28 -0
  4. package/README.md +55 -0
  5. package/app/app.config.ts +441 -0
  6. package/app/app.vue +18 -0
  7. package/app/assets/css/connect-base-layout.css +13 -0
  8. package/app/assets/css/connect-base-tw.css +303 -0
  9. package/app/components/Connect/Address/Display.vue +84 -0
  10. package/app/components/Connect/BCGovLogo.vue +42 -0
  11. package/app/components/Connect/Breadcrumb.vue +68 -0
  12. package/app/components/Connect/ButtonControl/Buttons.vue +28 -0
  13. package/app/components/Connect/ButtonControl/Index.vue +66 -0
  14. package/app/components/Connect/ButtonControl/Wrapper.vue +18 -0
  15. package/app/components/Connect/Contact/Display.vue +65 -0
  16. package/app/components/Connect/FloatingLabel.vue +67 -0
  17. package/app/components/Connect/Footer.vue +99 -0
  18. package/app/components/Connect/Header/LogoHomeLink.vue +20 -0
  19. package/app/components/Connect/Header/WhatsNew.vue +36 -0
  20. package/app/components/Connect/Header/Wrapper.vue +14 -0
  21. package/app/components/Connect/Header/index.vue +17 -0
  22. package/app/components/Connect/I18n/Helper.vue +40 -0
  23. package/app/components/Connect/Input.vue +69 -0
  24. package/app/components/Connect/InputDate.vue +104 -0
  25. package/app/components/Connect/Layout/Index.vue +25 -0
  26. package/app/components/Connect/Layout/Sidebar.vue +38 -0
  27. package/app/components/Connect/LocaleSelect.vue +37 -0
  28. package/app/components/Connect/Modal/Error.vue +76 -0
  29. package/app/components/Connect/Modal/index.vue +51 -0
  30. package/app/components/Connect/PageSection.vue +57 -0
  31. package/app/components/Connect/Select.vue +28 -0
  32. package/app/components/Connect/Slideover/WhatsNew.vue +61 -0
  33. package/app/components/Connect/Spinner.vue +91 -0
  34. package/app/components/Connect/SystemBanner.vue +53 -0
  35. package/app/components/Connect/Textarea.vue +27 -0
  36. package/app/components/Connect/Tombstone/Item.vue +51 -0
  37. package/app/components/Connect/Tombstone/index.vue +123 -0
  38. package/app/components/Connect/Transition/Collapse.vue +39 -0
  39. package/app/components/Connect/Transition/Fade.vue +31 -0
  40. package/app/components/HelloWorld/Index.vue +14 -0
  41. package/app/composables/useConnectButtonControl.ts +146 -0
  42. package/app/composables/useConnectLaunchDarkly.ts +173 -0
  43. package/app/composables/useConnectModal.ts +13 -0
  44. package/app/composables/useConnectTombstone.ts +25 -0
  45. package/app/coverage/app/app.config.ts.html +1408 -0
  46. package/app/coverage/app/app.vue.html +139 -0
  47. package/app/coverage/app/components/Connect/Address/Display.vue.html +337 -0
  48. package/app/coverage/app/components/Connect/Address/index.html +116 -0
  49. package/app/coverage/app/components/Connect/BCGovLogo.vue.html +211 -0
  50. package/app/coverage/app/components/Connect/Breadcrumb.vue.html +289 -0
  51. package/app/coverage/app/components/Connect/ButtonControl/Buttons.vue.html +166 -0
  52. package/app/coverage/app/components/Connect/ButtonControl/Index.vue.html +283 -0
  53. package/app/coverage/app/components/Connect/ButtonControl/Wrapper.vue.html +139 -0
  54. package/app/coverage/app/components/Connect/ButtonControl/index.html +146 -0
  55. package/app/coverage/app/components/Connect/Contact/Display.vue.html +280 -0
  56. package/app/coverage/app/components/Connect/Contact/index.html +116 -0
  57. package/app/coverage/app/components/Connect/FloatingLabel.vue.html +286 -0
  58. package/app/coverage/app/components/Connect/Footer.vue.html +382 -0
  59. package/app/coverage/app/components/Connect/Header/LogoHomeLink.vue.html +145 -0
  60. package/app/coverage/app/components/Connect/Header/WhatsNew.vue.html +193 -0
  61. package/app/coverage/app/components/Connect/Header/Wrapper.vue.html +127 -0
  62. package/app/coverage/app/components/Connect/Header/index.html +161 -0
  63. package/app/coverage/app/components/Connect/Header/index.vue.html +136 -0
  64. package/app/coverage/app/components/Connect/I18n/Helper.vue.html +205 -0
  65. package/app/coverage/app/components/Connect/I18n/index.html +116 -0
  66. package/app/coverage/app/components/Connect/Input.vue.html +262 -0
  67. package/app/coverage/app/components/Connect/InputDate.vue.html +406 -0
  68. package/app/coverage/app/components/Connect/Layout/Index.vue.html +160 -0
  69. package/app/coverage/app/components/Connect/Layout/Sidebar.vue.html +199 -0
  70. package/app/coverage/app/components/Connect/Layout/index.html +131 -0
  71. package/app/coverage/app/components/Connect/LocaleSelect.vue.html +196 -0
  72. package/app/coverage/app/components/Connect/Modal/Error.vue.html +313 -0
  73. package/app/coverage/app/components/Connect/Modal/index.html +131 -0
  74. package/app/coverage/app/components/Connect/Modal/index.vue.html +238 -0
  75. package/app/coverage/app/components/Connect/PageSection.vue.html +256 -0
  76. package/app/coverage/app/components/Connect/Select.vue.html +169 -0
  77. package/app/coverage/app/components/Connect/Slideover/WhatsNew.vue.html +268 -0
  78. package/app/coverage/app/components/Connect/Slideover/index.html +116 -0
  79. package/app/coverage/app/components/Connect/Spinner.vue.html +358 -0
  80. package/app/coverage/app/components/Connect/SystemBanner.vue.html +244 -0
  81. package/app/coverage/app/components/Connect/Textarea.vue.html +160 -0
  82. package/app/coverage/app/components/Connect/Tombstone/Item.vue.html +238 -0
  83. package/app/coverage/app/components/Connect/Tombstone/index.html +131 -0
  84. package/app/coverage/app/components/Connect/Tombstone/index.vue.html +454 -0
  85. package/app/coverage/app/components/Connect/Transition/Collapse.vue.html +202 -0
  86. package/app/coverage/app/components/Connect/Transition/Fade.vue.html +178 -0
  87. package/app/coverage/app/components/Connect/Transition/index.html +131 -0
  88. package/app/coverage/app/components/Connect/index.html +281 -0
  89. package/app/coverage/app/components/HelloWorld/Index.vue.html +127 -0
  90. package/app/coverage/app/components/HelloWorld/index.html +116 -0
  91. package/app/coverage/app/composables/index.html +161 -0
  92. package/app/coverage/app/composables/useConnectButtonControl.ts.html +523 -0
  93. package/app/coverage/app/composables/useConnectLaunchDarkly.ts.html +595 -0
  94. package/app/coverage/app/composables/useConnectModal.ts.html +124 -0
  95. package/app/coverage/app/composables/useConnectTombstone.ts.html +160 -0
  96. package/app/coverage/app/error.vue.html +247 -0
  97. package/app/coverage/app/index.html +146 -0
  98. package/app/coverage/app/interfaces/connect-address.ts.html +121 -0
  99. package/app/coverage/app/interfaces/connect-breadcrumb.ts.html +100 -0
  100. package/app/coverage/app/interfaces/connect-button-control.ts.html +151 -0
  101. package/app/coverage/app/interfaces/connect-contact-item.ts.html +103 -0
  102. package/app/coverage/app/interfaces/connect-modal.ts.html +100 -0
  103. package/app/coverage/app/interfaces/connect-tombstone.ts.html +208 -0
  104. package/app/coverage/app/interfaces/connect-whatsnew.ts.html +127 -0
  105. package/app/coverage/app/interfaces/index.html +206 -0
  106. package/app/coverage/app/layouts/ConnectBase.vue.html +100 -0
  107. package/app/coverage/app/layouts/index.html +116 -0
  108. package/app/coverage/app/plugins/index.html +146 -0
  109. package/app/coverage/app/plugins/maska.client.ts.html +115 -0
  110. package/app/coverage/app/plugins/sanitize.client.ts.html +151 -0
  111. package/app/coverage/app/plugins/whats-new.client.ts.html +220 -0
  112. package/app/coverage/app/types/dates.ts.html +136 -0
  113. package/app/coverage/app/types/index.html +116 -0
  114. package/app/coverage/app/utils/appendUrlParam.ts.html +97 -0
  115. package/app/coverage/app/utils/getErrorMessage.ts.html +211 -0
  116. package/app/coverage/app/utils/getErrorStatus.ts.html +196 -0
  117. package/app/coverage/app/utils/index.html +191 -0
  118. package/app/coverage/app/utils/logFetchError.ts.html +193 -0
  119. package/app/coverage/app/utils/parseSpecialCharacters.ts.html +160 -0
  120. package/app/coverage/app/utils/setBreadcrumbs.ts.html +100 -0
  121. package/app/coverage/base.css +224 -0
  122. package/app/coverage/block-navigation.js +87 -0
  123. package/app/coverage/clover.xml +2094 -0
  124. package/app/coverage/coverage-final.json +58 -0
  125. package/app/coverage/favicon.png +0 -0
  126. package/app/coverage/index.html +386 -0
  127. package/app/coverage/prettify.css +1 -0
  128. package/app/coverage/prettify.js +2 -0
  129. package/app/coverage/sort-arrow-sprite.png +0 -0
  130. package/app/coverage/sorter.js +210 -0
  131. package/app/error.vue +54 -0
  132. package/app/interfaces/connect-address.ts +12 -0
  133. package/app/interfaces/connect-breadcrumb.ts +5 -0
  134. package/app/interfaces/connect-button-control.ts +23 -0
  135. package/app/interfaces/connect-contact-item.ts +6 -0
  136. package/app/interfaces/connect-modal.ts +5 -0
  137. package/app/interfaces/connect-tombstone.ts +41 -0
  138. package/app/interfaces/connect-whatsnew.ts +14 -0
  139. package/app/layouts/ConnectBase.vue +5 -0
  140. package/app/plugins/maska.client.ts +10 -0
  141. package/app/plugins/sanitize.client.ts +22 -0
  142. package/app/plugins/whats-new.client.ts +45 -0
  143. package/app/spa-loading-template.html +61 -0
  144. package/app/types/base-app-config.d.ts +15 -0
  145. package/app/types/base-directives.d.ts +7 -0
  146. package/app/types/base-page-meta.d.ts +8 -0
  147. package/app/types/dates.ts +17 -0
  148. package/app/utils/appendUrlParam.ts +4 -0
  149. package/app/utils/getErrorMessage.ts +42 -0
  150. package/app/utils/getErrorStatus.ts +37 -0
  151. package/app/utils/logFetchError.ts +36 -0
  152. package/app/utils/parseSpecialCharacters.ts +25 -0
  153. package/app/utils/setBreadcrumbs.ts +7 -0
  154. package/firebase.json +34 -0
  155. package/i18n/locales/en-CA.ts +70 -0
  156. package/i18n/locales/fr-CA.ts +96 -0
  157. package/nuxt.config.ts +147 -0
  158. package/package.json +49 -0
  159. package/public/BCGovLogo/gov_bc_logo_horiz_en.png +0 -0
  160. package/public/BCGovLogo/gov_bc_logo_horiz_fr.png +0 -0
  161. package/public/BCGovLogo/gov_bc_logo_vert_en.png +0 -0
  162. package/public/BCGovLogo/gov_bc_logo_vert_fr.png +0 -0
  163. package/public/favicon.ico +0 -0
  164. package/public/fonts/BCSans/BCSans-Bold.woff +0 -0
  165. package/public/fonts/BCSans/BCSans-Bold.woff2 +0 -0
  166. package/public/fonts/BCSans/BCSans-BoldItalic.woff +0 -0
  167. package/public/fonts/BCSans/BCSans-BoldItalic.woff2 +0 -0
  168. package/public/fonts/BCSans/BCSans-Italic.woff +0 -0
  169. package/public/fonts/BCSans/BCSans-Italic.woff2 +0 -0
  170. package/public/fonts/BCSans/BCSans-Regular.woff +0 -0
  171. package/public/fonts/BCSans/BCSans-Regular.woff2 +0 -0
  172. package/public/fonts/BCSans/LICENSE_OFL.txt +94 -0
  173. package/test-results/.last-run.json +7 -0
  174. package/test-results/error-page-404-Error-vue-D-1eb51-cted-visuals-with-404-error-Desktop-Chrome/error-context.md +135 -0
  175. package/test-results/error-page-404-Error-vue-D-1eb51-cted-visuals-with-404-error-Desktop-Chrome/test-failed-1.png +0 -0
  176. package/test-results/error-page-404-Error-vue-D-1eb51-cted-visuals-with-404-error-Desktop-Chrome/trace.zip +0 -0
  177. package/test-results/error-page-500-Error-vue-D-8c2cf-cted-visuals-with-500-error-Desktop-Chrome/error-context.md +135 -0
  178. package/test-results/error-page-500-Error-vue-D-8c2cf-cted-visuals-with-500-error-Desktop-Chrome/test-failed-1.png +0 -0
  179. 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,12 @@
1
+ export interface ConnectAddress {
2
+ street: string
3
+ streetAdditional: string
4
+ city: string
5
+ region: string
6
+ postalCode: string
7
+ country: string
8
+ locationDescription: string
9
+ streetName?: string
10
+ streetNumber?: string
11
+ unitNumber?: string
12
+ }
@@ -0,0 +1,5 @@
1
+ import type { BreadcrumbItem } from '#ui/types'
2
+
3
+ export interface ConnectBreadcrumb extends BreadcrumbItem {
4
+ appendAccountId?: boolean
5
+ }
@@ -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,6 @@
1
+ export interface ConnectContactItem {
2
+ type: 'email' | 'phone' | 'fax'
3
+ title: string
4
+ value: string
5
+ href?: string
6
+ }
@@ -0,0 +1,5 @@
1
+ import type { ButtonProps } from '@nuxt/ui'
2
+
3
+ export interface ConnectModalButton extends ButtonProps {
4
+ shouldClose?: boolean
5
+ }
@@ -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,5 @@
1
+ <template>
2
+ <ConnectLayout>
3
+ <slot />
4
+ </ConnectLayout>
5
+ </template>
@@ -0,0 +1,10 @@
1
+ // https://beholdr.github.io/maska/v3/#/vue
2
+ import { vMaska } from 'maska/vue'
3
+
4
+ export default defineNuxtPlugin({
5
+ name: 'core-maska-plugin',
6
+ parallel: true,
7
+ setup(nuxtApp) {
8
+ nuxtApp.vueApp.directive('maska', vMaska)
9
+ }
10
+ })
@@ -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,15 @@
1
+ declare module '@nuxt/schema' {
2
+ interface AppConfigInput {
3
+ connect?: {
4
+ header?: {
5
+ localeSelect?: boolean
6
+ whatsNew?: boolean
7
+ }
8
+ footer?: {
9
+ versions?: string[]
10
+ }
11
+ }
12
+ }
13
+ }
14
+
15
+ export {}
@@ -0,0 +1,7 @@
1
+ declare module 'vue' {
2
+ interface GlobalDirectives {
3
+ vSanitize: Directive<HTMLElement, string>
4
+ }
5
+ }
6
+
7
+ export {}
@@ -0,0 +1,8 @@
1
+ declare module '#app' {
2
+ interface PageMeta {
3
+ breadcrumbs?: ConnectBreadcrumb[]
4
+ hideBreadcrumbs?: boolean
5
+ }
6
+ }
7
+
8
+ export {}
@@ -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,4 @@
1
+ export function appendUrlParam(url: string, key: string, value: string | number) {
2
+ const separator = url.includes('?') ? '&' : '?'
3
+ return `${url}${separator}${key}=${value}`
4
+ }
@@ -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
+ }