@7365admin1/layer-common 3.2.7 → 3.2.8-staging.201

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 (283) hide show
  1. package/assets/css/primitives.css +1113 -0
  2. package/assets/css/screens.css +1097 -0
  3. package/assets/css/shell.css +258 -0
  4. package/assets/css/tokens.css +236 -0
  5. package/components/AcceptDialog.vue +17 -18
  6. package/components/AccessCard/AvailableStats.vue +24 -16
  7. package/components/AccessCardAddForm.vue +42 -42
  8. package/components/AccessCardAssignToUnitForm.vue +13 -21
  9. package/components/AccessCardDeleteDialog.vue +41 -39
  10. package/components/AccessCardDetailsDialog.vue +125 -79
  11. package/components/AccessCardPreviewDialog.vue +58 -84
  12. package/components/AccessCardQrTagging.vue +60 -63
  13. package/components/AccessCardReplaceForm.vue +27 -23
  14. package/components/AccessManagement.vue +86 -85
  15. package/components/AddEqupmentForm.vue +18 -22
  16. package/components/AddPassKeyToVisitor.vue +95 -73
  17. package/components/AppButton.vue +62 -0
  18. package/components/AppCard.vue +20 -0
  19. package/components/AppDateField.vue +49 -0
  20. package/components/AppField.vue +76 -0
  21. package/components/AppKpiTile.vue +65 -0
  22. package/components/AppSegmented.vue +38 -0
  23. package/components/AppSelect.vue +247 -0
  24. package/components/AreaChecklistHistoryLogs.vue +51 -44
  25. package/components/AreaChecklistHistoryMain.vue +14 -7
  26. package/components/AreaFormDialog.vue +71 -59
  27. package/components/AreaMain.vue +73 -107
  28. package/components/AttachmentsViewer.vue +84 -22
  29. package/components/AttendanceCheckInOutDialog.vue +85 -88
  30. package/components/AttendanceDetailsDialog.vue +57 -63
  31. package/components/AttendanceMain.vue +4 -3
  32. package/components/AttendanceMapSearchDialog.vue +42 -44
  33. package/components/AttendanceSettingsDialog.vue +79 -63
  34. package/components/Avatar/Main.vue +54 -2
  35. package/components/BillingMain.vue +26 -30
  36. package/components/BtnUploadFile.vue +53 -11
  37. package/components/BuildingForm.vue +34 -27
  38. package/components/BuildingManagement/buildings.vue +39 -39
  39. package/components/BuildingManagement/units.vue +26 -42
  40. package/components/BuildingUnitFormAdd.vue +20 -23
  41. package/components/BuildingUnitFormEdit.vue +8 -4
  42. package/components/BulletinBoardForm.vue +19 -24
  43. package/components/BulletinBoardManagement.vue +49 -57
  44. package/components/BulletinBoardView.vue +9 -6
  45. package/components/BulletinExpirationChip.vue +13 -4
  46. package/components/Button/Close.vue +2 -2
  47. package/components/CameraForm.vue +21 -22
  48. package/components/CameraMain.vue +50 -49
  49. package/components/Card/DeleteConfirmation.vue +24 -22
  50. package/components/Card/MemberInfoSummary.vue +28 -3
  51. package/components/Card/Toggle.vue +15 -3
  52. package/components/CardToolbar.vue +37 -0
  53. package/components/Carousel.vue +56 -36
  54. package/components/Chat/Bubbles.vue +43 -14
  55. package/components/Chat/Information.vue +49 -32
  56. package/components/Chat/ListCard.vue +38 -9
  57. package/components/Chat/Message.vue +41 -15
  58. package/components/Chat/Navigation.vue +27 -15
  59. package/components/Chat/SkeletonLoader.vue +9 -2
  60. package/components/ChecklistItemRow.vue +25 -1
  61. package/components/CleaningScheduleMain.vue +44 -49
  62. package/components/ClientDetailForm.vue +81 -56
  63. package/components/ClientMain.vue +92 -82
  64. package/components/ConfirmDialog.vue +14 -8
  65. package/components/Container/Standard.vue +9 -7
  66. package/components/CreateSubsciptionPlan.vue +148 -89
  67. package/components/DashboardEmptyState.vue +48 -3
  68. package/components/DashboardMain.vue +924 -676
  69. package/components/DashboardPanel.vue +84 -21
  70. package/components/DeliveryCompany.vue +34 -20
  71. package/components/Dialog/DeleteConfirmation.vue +20 -21
  72. package/components/Dialog/ReplaceAutofillPrompt.vue +19 -26
  73. package/components/Dialog/ReusablePrompt.vue +30 -29
  74. package/components/Dialog/UpdateMoreAction.vue +62 -59
  75. package/components/DocumentForm.vue +14 -21
  76. package/components/DocumentManagement.vue +162 -108
  77. package/components/DocumentViewer.vue +29 -12
  78. package/components/DrawImage.vue +29 -21
  79. package/components/EntryPass/QrTemplatePreview.vue +27 -16
  80. package/components/EntryPassInformation.vue +40 -10
  81. package/components/EntryPassMain.vue +49 -27
  82. package/components/EquipmentItemMain.vue +84 -97
  83. package/components/EquipmentManagementMain.vue +81 -103
  84. package/components/Facility/BookingSetup.vue +43 -52
  85. package/components/Feedback/Form.vue +26 -12
  86. package/components/FeedbackMain.vue +35 -49
  87. package/components/FileInput.vue +75 -58
  88. package/components/FileInputWithList.vue +59 -12
  89. package/components/FormDialog.vue +60 -21
  90. package/components/HidAccessLogDashboard.vue +512 -490
  91. package/components/HidCameraCaptureDialog.vue +216 -0
  92. package/components/HidEnabledGate.vue +67 -9
  93. package/components/HidIntercomManagement.vue +1322 -653
  94. package/components/HidQrCodeConfiguration.vue +1030 -258
  95. package/components/HidReaderForm.vue +142 -59
  96. package/components/HidReaderManagement.vue +211 -323
  97. package/components/HidServiceSettingsPanel.vue +44 -13
  98. package/components/HidUserEnrollment.vue +1185 -412
  99. package/components/HygieneUpdateMoreAction.vue +44 -44
  100. package/components/IncidentReport/Authorities.vue +30 -18
  101. package/components/IncidentReport/IncidentInformation.vue +10 -10
  102. package/components/IncidentReport/IncidentInformationDownload.vue +3 -4
  103. package/components/IncidentReport/affectedEntities.vue +41 -20
  104. package/components/Input/Date.vue +3 -0
  105. package/components/Input/DatePicker.vue +1 -2
  106. package/components/Input/DateTimePicker.vue +1 -1
  107. package/components/Input/File.vue +55 -10
  108. package/components/Input/FileV2.vue +39 -6
  109. package/components/Input/InputPhoneNumberV2.vue +2 -1
  110. package/components/Input/ListGroupSelection.vue +56 -8
  111. package/components/Input/NRICNumber.vue +1 -0
  112. package/components/Input/NewDate.vue +1 -0
  113. package/components/Input/Number.vue +1 -0
  114. package/components/Input/Password.vue +1 -0
  115. package/components/Input/PhoneNumber.vue +21 -1
  116. package/components/Input/VehicleNumber.vue +1 -0
  117. package/components/InputLabel.vue +2 -2
  118. package/components/InvitationClientForm.vue +21 -19
  119. package/components/InvitationForm.vue +15 -21
  120. package/components/InvitationMain.vue +46 -61
  121. package/components/Layout/Header.vue +263 -36
  122. package/components/Layout/NavigationDrawer.vue +300 -30
  123. package/components/ListItem.vue +33 -3
  124. package/components/LocalPagination.vue +27 -3
  125. package/components/ManageChecklistMain.vue +136 -86
  126. package/components/MemberInformation.vue +32 -16
  127. package/components/MemberMain.vue +113 -93
  128. package/components/MyAttendanceMain.vue +3 -5
  129. package/components/NavigationItem.vue +83 -3
  130. package/components/Nfc/NFCPatrolReportMain.vue +47 -33
  131. package/components/Nfc/NFCPatrolRouteForm.vue +18 -20
  132. package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
  133. package/components/Nfc/NFCPatrolSettings.vue +14 -4
  134. package/components/Nfc/NFCTagForm.vue +22 -8
  135. package/components/Nfc/NFCTagMain.vue +41 -43
  136. package/components/Nfc/PatrolReport/DailyReportTab.vue +8 -1
  137. package/components/Nfc/PatrolReport/MonthlyReportTab.vue +8 -1
  138. package/components/Nfc/PatrolReport/MonthlyReportTable.vue +5 -3
  139. package/components/Nfc/PatrolReport/PatrolActivityTable.vue +26 -6
  140. package/components/Nfc/PatrolReport/PatrolReportTab.vue +8 -0
  141. package/components/Nfc/PatrolReport/PatrolRouteSummary.vue +41 -9
  142. package/components/Nfc/PatrolReport/RemarksDialog.vue +39 -9
  143. package/components/Nfc/PatrolReport/ReportEmptyState.vue +29 -9
  144. package/components/Nfc/PatrolReport/ReportFilters.vue +19 -10
  145. package/components/Nfc/PatrolReport/ReportLocationHeader.vue +32 -6
  146. package/components/Nfc/PatrolReport/SummaryReportTable.vue +28 -6
  147. package/components/NotificationSettings.vue +373 -0
  148. package/components/NumberSettingField.vue +9 -2
  149. package/components/OnlineFormConfigurationForm.vue +725 -237
  150. package/components/OnlineFormFill.vue +213 -106
  151. package/components/OnlineFormRenderPreview.vue +25 -30
  152. package/components/OnlineFormsConfiguration.vue +113 -109
  153. package/components/OrgViewDialog.vue +34 -16
  154. package/components/OvernightParkingAvailability.vue +49 -26
  155. package/components/OvernightParkingManagement.vue +78 -18
  156. package/components/PageHeader.vue +43 -0
  157. package/components/PassInformation.vue +109 -53
  158. package/components/PasswordConfirmation.vue +44 -26
  159. package/components/PeopleForm.vue +41 -20
  160. package/components/PhotoUpload.vue +110 -48
  161. package/components/PlaceholderComponent.vue +35 -19
  162. package/components/PlanActionsMenu.vue +12 -2
  163. package/components/PlanStatusBadge.vue +7 -8
  164. package/components/PlateNumberDisplay.vue +9 -6
  165. package/components/QrTemplate/PrintDialog.vue +66 -43
  166. package/components/RolePermissionFormCreate.vue +104 -79
  167. package/components/RolePermissionFormPreviewUpdate.vue +111 -109
  168. package/components/RolePermissionMain.vue +183 -81
  169. package/components/ScanVisitorQRCode.vue +99 -35
  170. package/components/ScheduleAreaMain.vue +34 -59
  171. package/components/ScheduleTaskForm.vue +61 -42
  172. package/components/ScheduleTaskMain.vue +56 -69
  173. package/components/SearchVehicleNumberUser.vue +39 -37
  174. package/components/ServiceProviderFormCreate.vue +29 -20
  175. package/components/ServiceProviderInvitationPrompt.vue +155 -0
  176. package/components/ServiceProviderMain.vue +601 -432
  177. package/components/Signature.vue +40 -34
  178. package/components/SignaturePad.vue +5 -29
  179. package/components/SiteSettings.vue +41 -11
  180. package/components/SiteSettingsWorkOrder.vue +61 -30
  181. package/components/SlideCardGroup.vue +35 -11
  182. package/components/Snackbar.vue +36 -3
  183. package/components/SpecificAttr.vue +18 -8
  184. package/components/StatusChip.vue +156 -0
  185. package/components/StockCard.vue +17 -12
  186. package/components/SubscriptionPlanTable.vue +207 -140
  187. package/components/SwitchContext.vue +201 -9
  188. package/components/TableHygiene.vue +36 -46
  189. package/components/TableList.vue +10 -10
  190. package/components/TableListSecondary.vue +105 -54
  191. package/components/TableMain.vue +251 -78
  192. package/components/TableV3.vue +61 -70
  193. package/components/TiptapEditor.vue +33 -40
  194. package/components/TiptapEditorQr.vue +5 -2
  195. package/components/Tooltip/Info.vue +69 -24
  196. package/components/UnitMain.vue +40 -60
  197. package/components/UnitPersonCard.vue +42 -13
  198. package/components/VehicleAddSelection.vue +48 -32
  199. package/components/VehicleForm.vue +112 -129
  200. package/components/VehicleManagement.vue +26 -12
  201. package/components/VehicleUpdateMoreAction.vue +59 -46
  202. package/components/VisitorDataFromScannedQRCodeForm.vue +90 -65
  203. package/components/VisitorForm.vue +774 -210
  204. package/components/VisitorFormSelection.vue +40 -37
  205. package/components/VisitorManagement.vue +239 -82
  206. package/components/VisitorPassKeyQRScanner.vue +65 -18
  207. package/components/VisitorSearchResultSummary.vue +60 -16
  208. package/components/VisitorSocketPopUp.vue +22 -14
  209. package/components/VisitorsReportPreview.vue +44 -29
  210. package/components/WorkOrder/Create.vue +26 -17
  211. package/components/WorkOrder/ListView.vue +38 -7
  212. package/components/WorkOrder/Main.vue +62 -59
  213. package/composables/useFeedback.ts +3 -3
  214. package/composables/useHidAmico.ts +269 -0
  215. package/composables/useHidNavigation.ts +2 -9
  216. package/composables/useMember.ts +3 -3
  217. package/composables/useNFCPatrolDailyReport.ts +13 -0
  218. package/composables/useNFCPatrolMonthlyReport.ts +22 -56
  219. package/composables/useNFCPatrolReport.ts +13 -0
  220. package/composables/useNotificationPreference.ts +87 -0
  221. package/composables/useOnlineFormPages.ts +2 -0
  222. package/composables/useRole.ts +22 -0
  223. package/composables/useServiceProvider.ts +3 -3
  224. package/composables/useServiceProviderInvitation.ts +145 -0
  225. package/composables/useSipWebPhone.ts +336 -0
  226. package/composables/useSubscriptionPlan.ts +62 -0
  227. package/composables/useThemePreference.ts +63 -0
  228. package/composables/useVisitor.ts +6 -1
  229. package/composables/useWebUsb.ts +127 -37
  230. package/composables/useWorkOrder.ts +17 -3
  231. package/error.vue +30 -6
  232. package/nuxt.config.ts +36 -0
  233. package/package.json +25 -4
  234. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -2
  235. package/pages/[org]/[site]/access-mgmt/{identity-mapping → hid-cards}/index.vue +2 -2
  236. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  237. package/pages/notification-settings.vue +19 -0
  238. package/pages/payment-method-linked.vue +34 -10
  239. package/pages/require-customer.vue +48 -10
  240. package/pages/require-organization-membership.vue +32 -8
  241. package/pages/unauthorized.vue +29 -4
  242. package/plugins/secure-member.client.ts +21 -56
  243. package/plugins/vuetify.ts +69 -2
  244. package/types/nfc-patrol-report.ts +3 -2
  245. package/types/site.d.ts +71 -0
  246. package/types/subscription.ts +18 -11
  247. package/utils/breadcrumb.test.ts +108 -0
  248. package/utils/breadcrumb.ts +82 -0
  249. package/utils/dashboard.test.ts +118 -0
  250. package/utils/dashboard.ts +53 -0
  251. package/utils/data.test.ts +95 -0
  252. package/utils/data.ts +40 -2
  253. package/utils/hid-discover.test.ts +102 -0
  254. package/utils/list-page.test.ts +60 -0
  255. package/utils/list-page.ts +38 -0
  256. package/utils/materialSymbols.ts +363 -0
  257. package/utils/nav-sections.test.ts +190 -0
  258. package/utils/nav-sections.ts +151 -0
  259. package/utils/nfc-patrol-report.test.ts +130 -0
  260. package/utils/nfc-patrol-report.ts +101 -0
  261. package/utils/role.test.ts +69 -0
  262. package/utils/role.ts +10 -0
  263. package/utils/route-name.test.ts +85 -0
  264. package/utils/route-name.ts +24 -0
  265. package/utils/status.test.ts +110 -0
  266. package/utils/status.ts +135 -0
  267. package/utils/table-card.test.ts +84 -0
  268. package/utils/theme-aa-ledger.ts +148 -0
  269. package/utils/theme.test.ts +485 -7
  270. package/utils/theme.ts +295 -110
  271. package/.changeset/README.md +0 -8
  272. package/.changeset/config.json +0 -11
  273. package/.editorconfig +0 -12
  274. package/.github/workflows/main.yml +0 -17
  275. package/.github/workflows/publish-staging.yml +0 -47
  276. package/.github/workflows/publish.yml +0 -39
  277. package/PUBLISHING.md +0 -269
  278. package/components/HidIdentityMapping.vue +0 -975
  279. package/components/ScheduleTaskAreaFormDialog.vue +0 -144
  280. package/components/ScheduleTaskAreaUpdateMoreAction.vue +0 -109
  281. package/components/ScheduleTastTicketMain.vue +0 -182
  282. package/components/TableWithButton.vue +0 -94
  283. package/test/visitor-socket.test.mjs +0 -36
@@ -0,0 +1,216 @@
1
+ <template>
2
+ <v-dialog
3
+ :model-value="modelValue"
4
+ max-width="520"
5
+ persistent
6
+ @update:model-value="emit('update:modelValue', $event)"
7
+ >
8
+ <!-- `rounded="lg"` is dropped, not replaced. Measured on the glass, the card
9
+ read 16px BEFORE and after: the `.rounded-lg` utility it compiled to was
10
+ already being overridden, so the prop stated a radius the card never
11
+ had. `.screen-modal` states the same 16px as `--r-modal`, on purpose. -->
12
+ <v-card class="camera-dialog screen-modal">
13
+ <v-card-title class="d-flex align-center justify-space-between">
14
+ <span>Take Facial Photo</span>
15
+ <v-btn
16
+ icon="mdi-close"
17
+ variant="text"
18
+ class="screen-modal__close"
19
+ aria-label="Close camera"
20
+ @click="close"
21
+ />
22
+ </v-card-title>
23
+
24
+ <v-card-text class="screen-modal__body">
25
+ <div class="camera-viewport">
26
+ <video
27
+ ref="videoElement"
28
+ class="camera-preview"
29
+ autoplay
30
+ muted
31
+ playsinline
32
+ />
33
+
34
+ <div v-if="starting" class="camera-state">
35
+ <v-progress-circular indeterminate color="primary" />
36
+ <span class="mt-3">Starting camera...</span>
37
+ </div>
38
+
39
+ <div v-else-if="cameraError" class="camera-state px-6 text-center">
40
+ <v-icon icon="mdi-camera-off-outline" size="42" color="error" />
41
+ <span class="mt-3 text-error">{{ cameraError }}</span>
42
+ </div>
43
+ </div>
44
+
45
+ <canvas ref="canvasElement" class="d-none" />
46
+
47
+ <div class="camera-dialog__hint text-center">
48
+ Keep your face centered and make sure the area is well lit.
49
+ </div>
50
+ </v-card-text>
51
+
52
+ <!-- `color="primary"` on a `v-btn` is the design's FOREGROUND token - the
53
+ link ink - not a fill, so the capture button was accent-on-accent.
54
+ `.screen-btn-primary` states the fill the design does draw. -->
55
+ <div class="screen-modal__footer">
56
+ <v-btn class="text-none screen-btn-ghost" variant="flat" @click="close">
57
+ Cancel
58
+ </v-btn>
59
+ <v-btn
60
+ class="text-none screen-btn-primary"
61
+ variant="flat"
62
+ prepend-icon="mdi-camera"
63
+ :disabled="starting || !!cameraError || !stream"
64
+ @click="capture"
65
+ >
66
+ Take Photo
67
+ </v-btn>
68
+ </div>
69
+ </v-card>
70
+ </v-dialog>
71
+ </template>
72
+
73
+ <script setup lang="ts">
74
+ const props = defineProps<{ modelValue: boolean }>();
75
+ const emit = defineEmits<{
76
+ "update:modelValue": [value: boolean];
77
+ capture: [dataUrl: string];
78
+ }>();
79
+
80
+ const videoElement = ref<HTMLVideoElement | null>(null);
81
+ const canvasElement = ref<HTMLCanvasElement | null>(null);
82
+ const stream = shallowRef<MediaStream | null>(null);
83
+ const starting = ref(false);
84
+ const cameraError = ref("");
85
+
86
+ function stopCamera() {
87
+ stream.value?.getTracks().forEach((track) => track.stop());
88
+ stream.value = null;
89
+ if (videoElement.value) videoElement.value.srcObject = null;
90
+ }
91
+
92
+ function getCameraErrorMessage(caught: unknown) {
93
+ if (caught instanceof DOMException) {
94
+ if (caught.name === "NotAllowedError") {
95
+ return "Camera permission was denied. Allow camera access and try again.";
96
+ }
97
+ if (caught.name === "NotFoundError") {
98
+ return "No camera was found on this device.";
99
+ }
100
+ if (caught.name === "NotReadableError") {
101
+ return "The camera is currently being used by another application.";
102
+ }
103
+ }
104
+ return "Unable to open the camera. Use HTTPS or check the browser camera permission.";
105
+ }
106
+
107
+ async function startCamera() {
108
+ stopCamera();
109
+ cameraError.value = "";
110
+ starting.value = true;
111
+
112
+ try {
113
+ if (!navigator.mediaDevices?.getUserMedia) {
114
+ throw new Error("Camera API is unavailable");
115
+ }
116
+
117
+ stream.value = await navigator.mediaDevices.getUserMedia({
118
+ audio: false,
119
+ video: {
120
+ facingMode: "user",
121
+ width: { ideal: 1280 },
122
+ height: { ideal: 960 },
123
+ },
124
+ });
125
+
126
+ await nextTick();
127
+ if (!videoElement.value) throw new Error("Camera preview is unavailable");
128
+ videoElement.value.srcObject = stream.value;
129
+ await videoElement.value.play();
130
+ } catch (caught: unknown) {
131
+ stopCamera();
132
+ cameraError.value = getCameraErrorMessage(caught);
133
+ } finally {
134
+ starting.value = false;
135
+ }
136
+ }
137
+
138
+ function capture() {
139
+ const video = videoElement.value;
140
+ const canvas = canvasElement.value;
141
+ if (!video || !canvas || !video.videoWidth || !video.videoHeight) return;
142
+
143
+ canvas.width = video.videoWidth;
144
+ canvas.height = video.videoHeight;
145
+ const context = canvas.getContext("2d");
146
+ if (!context) return;
147
+
148
+ context.translate(canvas.width, 0);
149
+ context.scale(-1, 1);
150
+ context.drawImage(video, 0, 0, canvas.width, canvas.height);
151
+
152
+ emit("capture", canvas.toDataURL("image/jpeg", 0.9));
153
+ close();
154
+ }
155
+
156
+ function close() {
157
+ stopCamera();
158
+ emit("update:modelValue", false);
159
+ }
160
+
161
+ watch(
162
+ () => props.modelValue,
163
+ (isOpen) => {
164
+ if (isOpen) void startCamera();
165
+ else stopCamera();
166
+ }
167
+ );
168
+
169
+ onBeforeUnmount(stopCamera);
170
+ </script>
171
+
172
+ <style scoped>
173
+ .camera-dialog {
174
+ overflow: hidden;
175
+ }
176
+
177
+ .camera-viewport {
178
+ position: relative;
179
+ width: 100%;
180
+ overflow: hidden;
181
+ aspect-ratio: 3 / 4;
182
+ border-radius: var(--r-inner);
183
+ /* Deliberately NOT a theme surface: this is the stage a video sits on, and it
184
+ stays dark in both themes so the preview does not flash white on light.
185
+ Its own `#ffffff` text below is paired with it, not with the theme. */
186
+ background: #111722;
187
+ }
188
+
189
+ /* The design's small helper line, not Vuetify's `text-caption` /
190
+ `text-medium-emphasis` pair. */
191
+ .camera-dialog__hint {
192
+ margin-top: 12px;
193
+ font-family: var(--font-sans);
194
+ font-size: var(--fs-cell);
195
+ font-weight: 500;
196
+ color: var(--muted);
197
+ }
198
+
199
+ .camera-preview {
200
+ width: 100%;
201
+ height: 100%;
202
+ object-fit: cover;
203
+ transform: scaleX(-1);
204
+ }
205
+
206
+ .camera-state {
207
+ position: absolute;
208
+ inset: 0;
209
+ display: flex;
210
+ flex-direction: column;
211
+ align-items: center;
212
+ justify-content: center;
213
+ background: #111722;
214
+ color: #ffffff;
215
+ }
216
+ </style>
@@ -1,14 +1,31 @@
1
+ <!--
2
+ The gate that stands in for a whole screen when HID is not enabled for a site.
3
+
4
+ What it was: a raw `v-alert type="info" variant="tonal"` — Vuetify's own tonal
5
+ slab, at Vuetify's 4px radius, in Vuetify's font, on a Vuetify blue the theme
6
+ does not control. It is a full-screen state, not an inline warning.
7
+
8
+ DESIGN GAP, STATED: the handoff draws no "service not enabled" page. So this
9
+ is assembled from the established primitive rather than invented, exactly as
10
+ `PlaceholderComponent` was — `.screen-empty` (`assets/css/screens.css:149`) is
11
+ the design's centred, muted, 52px-padded block for "there is nothing to show
12
+ here", and the heading takes the `--fs-card-title` / `--fw-card-title` type
13
+ the design uses for every panel heading.
14
+
15
+ `.tone-info` sets `--tone-bg`; the icon's soft disc is the only thing that
16
+ paints with it, so the block keeps the informational colour without becoming
17
+ a coloured slab. Icons are translated centrally — no hand-mapping here.
18
+ -->
1
19
  <template>
2
20
  <slot v-if="hidEnabled" />
3
21
 
4
- <v-alert
5
- v-else
6
- type="info"
7
- variant="tonal"
8
- class="mt-6"
9
- title="HID service is not enabled"
10
- :text="message"
11
- />
22
+ <div v-else class="screen-empty hid-gate">
23
+ <span class="hid-gate__icon tone-info">
24
+ <v-icon icon="mdi-information-outline" size="24" />
25
+ </span>
26
+ <h2 class="hid-gate__title">HID service is not enabled</h2>
27
+ <p class="hid-gate__body">{{ message }}</p>
28
+ </div>
12
29
  </template>
13
30
 
14
31
  <script setup lang="ts">
@@ -28,7 +45,11 @@ const props = withDefaults(
28
45
  const { getSiteById } = useSiteSettings();
29
46
  const { getHidServiceEnabled } = useOptionalServices();
30
47
 
31
- const { data: siteData } = await useLazyAsyncData(
48
+ // Do not await this request during route setup. Every HID page uses this gate;
49
+ // awaiting it makes Nuxt Suspense keep rendering the previous page until the
50
+ // site request finishes, so the URL changes while the visible module appears
51
+ // frozen. The lazy request updates `siteData` reactively after navigation.
52
+ const { data: siteData } = useLazyAsyncData(
32
53
  "hid-enabled-gate-site-" + props.site,
33
54
  () => getSiteById(props.site),
34
55
  {
@@ -49,3 +70,40 @@ watchEffect(() => {
49
70
  );
50
71
  });
51
72
  </script>
73
+
74
+ <style scoped>
75
+ /* It replaces a screen's whole body, so it holds the viewport rather than
76
+ collapsing to the height of three lines. Same reason as PlaceholderComponent. */
77
+ .hid-gate {
78
+ min-height: 40vh;
79
+ }
80
+
81
+ .hid-gate__icon {
82
+ display: inline-flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ width: 44px;
86
+ height: 44px;
87
+ border-radius: 50%;
88
+ background: var(--tone-bg);
89
+ }
90
+
91
+ /* `.screen-empty .v-icon` paints `--muted`; inside the tone scope the icon is
92
+ the informational colour, so it has to win that one-class rule. */
93
+ .hid-gate .hid-gate__icon .v-icon {
94
+ color: var(--info);
95
+ }
96
+
97
+ .hid-gate__title {
98
+ margin: 0;
99
+ font-size: var(--fs-card-title);
100
+ font-weight: var(--fw-card-title);
101
+ color: var(--text);
102
+ }
103
+
104
+ .hid-gate__body {
105
+ max-width: 46ch;
106
+ margin: 0;
107
+ font-weight: var(--fw-cell);
108
+ }
109
+ </style>