@7365admin1/layer-common 3.2.1-staging.71 → 3.2.1

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 (46) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/components/EntryPassInformation.vue +4 -4
  3. package/components/HidAccessLogDashboard.vue +44 -119
  4. package/components/HidIdentityMapping.vue +975 -0
  5. package/components/HidIntercomManagement.vue +201 -808
  6. package/components/HidQrCodeConfiguration.vue +95 -907
  7. package/components/HidServiceSettingsPanel.vue +1 -20
  8. package/components/HidUserEnrollment.vue +81 -392
  9. package/components/Layout/NavigationDrawer.vue +2 -43
  10. package/components/NavigationItem.vue +0 -9
  11. package/components/Nfc/NFCPatrolRouteMain.vue +7 -52
  12. package/components/OnlineFormConfigurationForm.vue +224 -620
  13. package/components/OnlineFormFill.vue +8 -36
  14. package/components/OnlineFormsConfiguration.vue +2 -6
  15. package/components/QrTemplate/CreditCardLandscape.vue +12 -19
  16. package/components/QrTemplate/PrintDialog.vue +196 -209
  17. package/components/SiteSettings.vue +2 -4
  18. package/components/VisitorForm.vue +78 -201
  19. package/components/VisitorManagement.vue +1 -0
  20. package/components/VisitorSocketPopUp.vue +2 -56
  21. package/composables/useHidAmico.ts +0 -127
  22. package/composables/useHidNavigation.ts +7 -0
  23. package/composables/useOnlineFormPages.ts +0 -2
  24. package/composables/useSiteSettings.ts +0 -50
  25. package/composables/useVisitorSocket.ts +0 -26
  26. package/composables/useWebUsb.ts +37 -127
  27. package/middleware/member.ts +4 -0
  28. package/package.json +1 -3
  29. package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +23 -0
  30. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  31. package/plugins/secure-member.client.ts +56 -21
  32. package/plugins/vuetify.ts +9 -37
  33. package/types/site.d.ts +0 -71
  34. package/.changeset/camera-wall-gated-endpoint.md +0 -47
  35. package/.changeset/camera-wall-plain-english.md +0 -60
  36. package/.changeset/camera-wall-selection-and-guidance.md +0 -65
  37. package/.changeset/dark-primary-contrast.md +0 -39
  38. package/components/CameraWall.vue +0 -1034
  39. package/components/CameraWallTile.vue +0 -818
  40. package/components/HidCameraCaptureDialog.vue +0 -199
  41. package/composables/useSipWebPhone.ts +0 -311
  42. package/test/visitor-socket.test.mjs +0 -36
  43. package/utils/camera-wall.test.ts +0 -571
  44. package/utils/camera-wall.ts +0 -926
  45. package/utils/theme.test.ts +0 -201
  46. package/utils/theme.ts +0 -136
@@ -20,8 +20,6 @@ export type FormBlock =
20
20
  | { type: "checkbox-group"; title?: string; single?: boolean; items: CheckboxItemDef[] }
21
21
  | { type: "paragraph"; text: string }
22
22
  | { type: "section-header"; text: string }
23
- | { type: "ordered-list"; title?: string; items: string[] }
24
- | { type: "unordered-list"; title?: string; items: string[] }
25
23
  | { type: "divider" }
26
24
  | { type: "signature-row"; label: string; key: string }
27
25
  | { type: "acknowledgement"; text: string }
@@ -99,54 +99,6 @@ export default function () {
99
99
  });
100
100
  }
101
101
 
102
- /**
103
- * The monitoring wall for one site.
104
- *
105
- * A different endpoint from `getAllSiteCameras`, and the difference is the
106
- * point. That one is the Settings panel's paginated CRUD list; this one is
107
- * purpose-built for a wall and is what the React Native app already uses:
108
- *
109
- * - it is **site-scoped and authorised server-side** — the caller must be a
110
- * member of the site, of its owning organisation, or work for an
111
- * organisation engaged to serve it;
112
- * - it returns `type: "ip"` cameras only, so an ANPR unit can never land on a
113
- * monitoring wall;
114
- * - it returns each camera's **capability descriptor** and the server's own
115
- * `unavailableReason`, so a tile explains itself instead of showing a black
116
- * rectangle;
117
- * - it never returns a credential, and it contacts no device.
118
- *
119
- * Unpaginated by design: a wall shows a site's cameras, and paging them was
120
- * an artefact of borrowing the CRUD list.
121
- */
122
- async function getSiteWall(siteId: string) {
123
- return await useNuxtApp().$api<Record<string, any>>(
124
- `/api/site-cameras/site/${siteId}/wall`,
125
- { method: "GET" }
126
- );
127
- }
128
-
129
- /**
130
- * Health for the cameras currently on screen.
131
- *
132
- * Same site scoping as the wall, and the ids are narrowed to the visible
133
- * tiles on purpose: this is the one camera call that can reach a device, and
134
- * asking about cameras nobody is looking at spends a real budget for nothing.
135
- *
136
- * **Reachability is answered per RECORDER and cached there**, so a wall of
137
- * twelve channels on one device is one connect rather than twelve, and
138
- * several supervisors watching the same wall is still one. That is what makes
139
- * it safe to ask on a timer at all — but only a slow one: the server caches
140
- * for `healthCacheSeconds`, and asking faster than the cache buys nothing and
141
- * costs round trips.
142
- */
143
- async function getSiteHealth(siteId: string, cameraIds: string[]) {
144
- return await useNuxtApp().$api<Record<string, any>>(
145
- `/api/site-cameras/site/${siteId}/health`,
146
- { method: "GET", query: { ids: cameraIds.join(",") } }
147
- );
148
- }
149
-
150
102
  async function updateSiteInformation(
151
103
  siteId: string,
152
104
  payload: { bgImage: string; description: string; docs: { id: string; name: string }[] }
@@ -223,8 +175,6 @@ export default function () {
223
175
  updateSite,
224
176
  addCamera,
225
177
  getAllSiteCameras,
226
- getSiteWall,
227
- getSiteHealth,
228
178
  setSiteGuardPosts,
229
179
  updateSiteCamera,
230
180
  deleteSiteCameraById,
@@ -5,32 +5,6 @@ export type TVisitorSocketData = {
5
5
  message?: string;
6
6
  messagePermanent?: string;
7
7
  reload?: boolean;
8
- /**
9
- * Camera health. Sent alongside `message` by API-core so the client can group
10
- * or clear an alert per camera instead of matching on the message text.
11
- * Absent on older API-core builds — treat a bare `message` as a fault.
12
- */
13
- event?: "camera-fault" | "camera-recovered";
14
- /** The camera the `event` is about. */
15
- camera?: string;
16
- };
17
-
18
- /**
19
- * One line for however many cameras are currently down.
20
- *
21
- * A site with several ANPR cameras could otherwise raise one alert per camera
22
- * at the same moment. The single-camera case shows the server's own message,
23
- * which names the camera; beyond that, naming them all in a snackbar is worse
24
- * than sending the operator to the page that lists them.
25
- */
26
- export const cameraAlertMessage = (messages: string[]): string => {
27
- if (!messages.length) return "";
28
- if (messages.length === 1) return messages[0] as string;
29
- return (
30
- `${messages.length} cameras at this site are not responding. Plate reads ` +
31
- `and automatic barrier opening are stopped for them. Open Site Settings > ` +
32
- `Cameras to see which.`
33
- );
34
8
  };
35
9
 
36
10
  export const useVisitorSocket = () => {
@@ -11,10 +11,6 @@ interface USBDevice {
11
11
  close(): Promise<void>;
12
12
  selectConfiguration(configurationValue: number): Promise<void>;
13
13
  claimInterface(interfaceNumber: number): Promise<void>;
14
- selectAlternateInterface(
15
- interfaceNumber: number,
16
- alternateSetting: number,
17
- ): Promise<void>;
18
14
  releaseInterface(interfaceNumber: number): Promise<void>;
19
15
  transferOut(
20
16
  endpointNumber: number,
@@ -23,27 +19,14 @@ interface USBDevice {
23
19
  configuration: {
24
20
  interfaces: Array<{
25
21
  interfaceNumber: number;
26
- alternate: USBAlternateInterface;
27
22
  alternates: Array<{
28
- alternateSetting: number;
29
- interfaceClass: number;
30
23
  endpoints: Array<{
31
24
  endpointNumber: number;
32
25
  direction: "in" | "out";
33
26
  }>;
34
27
  }>;
35
28
  }>;
36
- } | null;
37
- configurations: Array<{ configurationValue: number }>;
38
- }
39
-
40
- interface USBAlternateInterface {
41
- alternateSetting: number;
42
- interfaceClass: number;
43
- endpoints: Array<{
44
- endpointNumber: number;
45
- direction: "in" | "out";
46
- }>;
29
+ };
47
30
  }
48
31
 
49
32
  interface USBOutTransferResult {
@@ -56,86 +39,13 @@ declare global {
56
39
  usb: {
57
40
  getDevices(): Promise<USBDevice[]>;
58
41
  requestDevice(options: {
59
- filters: Array<{
60
- vendorId?: number;
61
- productId?: number;
62
- classCode?: number;
63
- }>;
42
+ filters: Array<{ vendorId?: number; productId?: number }>;
64
43
  }): Promise<USBDevice>;
65
44
  };
66
45
  }
67
46
  }
68
47
 
69
48
  export default function useWebUsb() {
70
- const printerConnections = new WeakMap<
71
- USBDevice,
72
- { interfaceNumber: number; endpointNumber: number }
73
- >();
74
-
75
- const protectedInterfaceClasses = new Set([0x01, 0x03, 0x08, 0x0b, 0x0e, 0x10, 0xe0]);
76
-
77
- function getErrorMessage(error: unknown, fallback: string) {
78
- return error instanceof Error && error.message ? error.message : fallback;
79
- }
80
-
81
- function findPrinterInterface(device: USBDevice) {
82
- const candidates = (device.configuration?.interfaces || []).flatMap((usbInterface) =>
83
- usbInterface.alternates.flatMap((alternate) => {
84
- const outputEndpoint = alternate.endpoints.find((endpoint) => endpoint.direction === "out");
85
- if (!outputEndpoint || protectedInterfaceClasses.has(alternate.interfaceClass)) return [];
86
-
87
- return [{
88
- interfaceNumber: usbInterface.interfaceNumber,
89
- alternateSetting: alternate.alternateSetting,
90
- activeAlternateSetting: usbInterface.alternate.alternateSetting,
91
- interfaceClass: alternate.interfaceClass,
92
- endpointNumber: outputEndpoint.endpointNumber,
93
- }];
94
- }),
95
- );
96
-
97
- candidates.sort((left, right) => {
98
- const priority = (interfaceClass: number) => interfaceClass === 0x07 ? 0 : interfaceClass === 0xff ? 1 : 2;
99
- return priority(left.interfaceClass) - priority(right.interfaceClass);
100
- });
101
-
102
- return candidates[0] || null;
103
- }
104
-
105
- async function preparePrinterDevice(device: USBDevice) {
106
- await device.open();
107
- if (!device.configuration) {
108
- await device.selectConfiguration(device.configurations[0]?.configurationValue || 1);
109
- }
110
-
111
- const printerInterface = findPrinterInterface(device);
112
- if (!printerInterface) {
113
- throw new Error(
114
- "The selected USB device is not a compatible printer. Select the receipt printer instead of a Bluetooth, camera, keyboard, or other system device.",
115
- );
116
- }
117
-
118
- await device.claimInterface(printerInterface.interfaceNumber);
119
- if (printerInterface.alternateSetting !== printerInterface.activeAlternateSetting) {
120
- await device.selectAlternateInterface(
121
- printerInterface.interfaceNumber,
122
- printerInterface.alternateSetting,
123
- );
124
- }
125
-
126
- printerConnections.set(device, {
127
- interfaceNumber: printerInterface.interfaceNumber,
128
- endpointNumber: printerInterface.endpointNumber,
129
- });
130
- return device;
131
- }
132
-
133
- function getPrinterConnection(device: USBDevice) {
134
- const connection = printerConnections.get(device);
135
- if (!connection) throw new Error("Printer interface is not connected");
136
- return connection;
137
- }
138
-
139
49
  const isWebUsbSupported = computed(() => {
140
50
  return typeof navigator !== "undefined" && "usb" in navigator;
141
51
  });
@@ -165,9 +75,7 @@ export default function useWebUsb() {
165
75
  throw new Error("Web USB is not supported in this browser");
166
76
  }
167
77
  try {
168
- const device = await navigator.usb.requestDevice({
169
- filters: [{ classCode: 0x07 }, { classCode: 0xff }],
170
- });
78
+ const device = await navigator.usb.requestDevice({ filters: [] });
171
79
  return {
172
80
  vendorId: device.vendorId,
173
81
  productId: device.productId,
@@ -176,12 +84,12 @@ export default function useWebUsb() {
176
84
  serialNumber: device.serialNumber || "Unknown",
177
85
  deviceId: `${device.vendorId}:${device.productId}`,
178
86
  };
179
- } catch (error: unknown) {
180
- if (error instanceof DOMException && error.name === "NotFoundError") {
87
+ } catch (error: any) {
88
+ if (error.name === "NotFoundError") {
181
89
  throw new Error("No device selected");
182
90
  }
183
91
  console.error("Error requesting USB device:", error);
184
- throw new Error(getErrorMessage(error, "Failed to access USB printer"));
92
+ throw new Error("Failed to access USB device");
185
93
  }
186
94
  };
187
95
 
@@ -202,17 +110,14 @@ export default function useWebUsb() {
202
110
  });
203
111
  }
204
112
  console.log("[WebUSB] Opening device:", device.vendorId, device.productId);
205
- await preparePrinterDevice(device);
113
+ await device.open();
114
+ await device.selectConfiguration(1);
115
+ await device.claimInterface(0);
206
116
  console.log("[WebUSB] Device opened and interface claimed.");
207
117
  return device;
208
- } catch (error: unknown) {
118
+ } catch (error) {
209
119
  console.error("Error connecting to USB device:", error);
210
- try {
211
- await device?.close();
212
- } catch (closeError) {
213
- console.error("Error closing rejected USB device:", closeError);
214
- }
215
- throw new Error(getErrorMessage(error, "Failed to connect to USB printer"));
120
+ throw new Error("Failed to connect to USB device");
216
121
  }
217
122
  };
218
123
 
@@ -268,20 +173,15 @@ export default function useWebUsb() {
268
173
  ? "Connection and print test successful"
269
174
  : "Connection successful but print test failed",
270
175
  };
271
- } catch (error: unknown) {
176
+ } catch (error: any) {
272
177
  return {
273
178
  success: false,
274
- error: getErrorMessage(error, "Failed to connect to USB printer"),
179
+ error: error.message,
275
180
  message: "Connection failed",
276
181
  };
277
182
  } finally {
278
183
  if (device) {
279
184
  try {
280
- const connection = printerConnections.get(device);
281
- if (connection) {
282
- await device.releaseInterface(connection.interfaceNumber);
283
- printerConnections.delete(device);
284
- }
285
185
  await device.close();
286
186
  } catch (closeError) {
287
187
  console.error("Error closing device:", closeError);
@@ -305,16 +205,21 @@ export default function useWebUsb() {
305
205
  ];
306
206
 
307
207
  const testData = new Uint8Array(testCommands);
308
- const { endpointNumber } = getPrinterConnection(device);
309
- await device.transferOut(endpointNumber, testData);
208
+ const usbInterface = device.configuration.interfaces[0];
209
+ const alternate = usbInterface.alternates[0];
210
+ const outputEndpoint = alternate.endpoints.find(
211
+ (ep: any) => ep.direction === "out",
212
+ );
213
+
214
+ if (!outputEndpoint) {
215
+ throw new Error("No output endpoint found");
216
+ }
217
+
218
+ await device.transferOut(outputEndpoint.endpointNumber, testData);
310
219
  return { success: true, message: "Test print sent successfully" };
311
- } catch (error: unknown) {
220
+ } catch (error: any) {
312
221
  console.error("Test print error:", error);
313
- return {
314
- success: false,
315
- error: getErrorMessage(error, "Test print failed"),
316
- message: "Test print failed",
317
- };
222
+ return { success: false, error: error.message, message: "Test print failed" };
318
223
  }
319
224
  };
320
225
 
@@ -449,8 +354,13 @@ export default function useWebUsb() {
449
354
  ) => {
450
355
  try {
451
356
  console.log("[WebUSB] printQrCode start — doorLevel:", doorLevel, "liftLevel:", liftLevel, "company:", companyName, "address:", address);
452
- const { endpointNumber } = getPrinterConnection(device);
453
- console.log("[WebUSB] Using endpoint:", endpointNumber);
357
+ const usbInterface = device.configuration.interfaces[0];
358
+ const alternate = usbInterface.alternates[0];
359
+ const outputEndpoint = alternate.endpoints.find(
360
+ (ep: any) => ep.direction === "out",
361
+ );
362
+ if (!outputEndpoint) throw new Error("No output endpoint found");
363
+ console.log("[WebUSB] Using endpoint:", outputEndpoint.endpointNumber);
454
364
 
455
365
  const canvas = await createReceiptLayout(
456
366
  urlImage,
@@ -465,17 +375,17 @@ export default function useWebUsb() {
465
375
  const rasterData = canvasToRaster(canvas);
466
376
  console.log("[WebUSB] Raster data size:", rasterData.byteLength, "bytes");
467
377
 
468
- await device.transferOut(endpointNumber, rasterData.buffer);
378
+ await device.transferOut(outputEndpoint.endpointNumber, rasterData.buffer);
469
379
  console.log("[WebUSB] Raster data sent.");
470
380
 
471
381
  const CUT = new Uint8Array([0x1d, 0x56, 0x41, 0x10]);
472
- await device.transferOut(endpointNumber, CUT.buffer);
382
+ await device.transferOut(outputEndpoint.endpointNumber, CUT.buffer);
473
383
  console.log("[WebUSB] Cut command sent.");
474
384
 
475
385
  return { success: true, message: "QR print sent successfully" };
476
- } catch (error: unknown) {
386
+ } catch (error: any) {
477
387
  console.error("[WebUSB] printQrCode error:", error);
478
- return { success: false, error: getErrorMessage(error, "Unable to print QR code") };
388
+ return { success: false, error: error.message };
479
389
  }
480
390
  };
481
391
 
@@ -0,0 +1,4 @@
1
+ export default defineNuxtRouteMiddleware(() => {
2
+ if (import.meta.server) return;
3
+ const adminMember = useCookie("admin-member").value ?? "";
4
+ });
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "3.2.1-staging.71",
5
+ "version": "3.2.1",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {
@@ -14,7 +14,6 @@
14
14
  "build": "nuxt build .playground",
15
15
  "generate": "nuxt generate .playground",
16
16
  "preview": "nuxt preview .playground",
17
- "test": "esbuild composables/useVisitorSocket.ts --format=esm --outfile=test/.build/useVisitorSocket.mjs --log-level=error && node --test \"test/*.test.mjs\"",
18
17
  "release": "yarn run build && changeset publish"
19
18
  },
20
19
  "devDependencies": {
@@ -41,7 +40,6 @@
41
40
  "qrcode": "^1.5.4",
42
41
  "qrcode.vue": "^3.4.1",
43
42
  "sass": "^1.80.6",
44
- "sip.js": "0.21.2",
45
43
  "socket.io-client": "^4.8.3",
46
44
  "vue-draggable-next": "^2.3.0",
47
45
  "vue3-signature": "^0.2.4",
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <v-container fluid>
3
+ <HidEnabledGate
4
+ :site="siteId"
5
+ :org="orgId"
6
+ message="Enable HID as a service for this site before mapping identities."
7
+ >
8
+ <HidIdentityMapping :site="siteId" :org="orgId" />
9
+ </HidEnabledGate>
10
+ </v-container>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ definePageMeta({
15
+ layout: "default",
16
+ middleware: ["01-auth", "02-org"],
17
+ memberOnly: true,
18
+ });
19
+
20
+ const route = useRoute();
21
+ const siteId = computed(() => String(route.params.site ?? ""));
22
+ const orgId = computed(() => String(route.params.org ?? ""));
23
+ </script>
@@ -5,7 +5,7 @@
5
5
  :org="orgId"
6
6
  message="Enable HID as a service for this site before configuring HID intercom."
7
7
  >
8
- <HidIntercomManagement :site="siteId" :org="orgId" />
8
+ <HidIntercomManagement :site="siteId" />
9
9
  </HidEnabledGate>
10
10
  </v-container>
11
11
  </template>
@@ -12,40 +12,75 @@ export default defineNuxtPlugin(() => {
12
12
 
13
13
  const { userAppRole, id, orgNature } = useLocalSetup();
14
14
 
15
- router.beforeEach(async (to) => {
15
+ router.afterEach(async (to) => {
16
16
  const isMember = to.meta?.memberOnly;
17
17
 
18
18
  if (!isMember) return;
19
19
 
20
20
  const APP = useRuntimeConfig().public.APP;
21
- const org =
22
- (to.params.org as string) || (to.params.organization as string) || "";
21
+ const org = computed(
22
+ () =>
23
+ (to.params.org as string) || (to.params.organization as string) || ""
24
+ );
23
25
 
24
- if (!hexSchema.safeParse(org).success) {
25
- return { name: "require-organization-membership" };
26
+ console.log('[secure-member-plugin', 'org', org.value)
27
+
28
+ if (!hexSchema.safeParse(org.value).success) {
29
+ return router.replace({ name: "require-organization-membership" });
26
30
  }
27
31
 
28
- const userId = useCookie("user").value ?? "";
29
- if (!userId) return { name: "index" };
32
+ const userId = computed(() => useCookie("user").value ?? "");
33
+
34
+ const { data: userMemberData, error: userMemberError } =
35
+ await useLazyAsyncData(
36
+ "plugin-get-member-by-id-" + userId.value + "-" + APP + "-" + org.value,
37
+ () => getByUserType(userId.value, APP, org.value),
38
+ { watch: [userId] }
39
+ );
40
+
41
+ watchEffect(() => {
42
+ if (userMemberError.value) {
43
+ console.log('running-secure-member-redirect-plugin')
44
+ navigateTo(
45
+ {
46
+ name: "index",
47
+ },
48
+ { replace: true }
49
+ );
50
+ }
51
+ });
52
+
53
+ const roleId = ref("roleId");
30
54
 
31
- try {
32
- const userMemberData = await getByUserType(userId, APP, org);
33
- id.value = userMemberData.org ?? "";
55
+ watchEffect(() => {
56
+ if (userMemberData.value) {
57
+ id.value = userMemberData.value.org ?? "";
58
+ roleId.value = userMemberData.value.role ?? "roleId";
59
+ }
60
+ });
34
61
 
35
- const [orgResult, roleResult] = await Promise.allSettled([
36
- getById(org),
37
- userMemberData.role ? getRoleById(userMemberData.role) : null,
38
- ]);
62
+ const { data: getOrgByIdReq } = await useLazyAsyncData(
63
+ "plugin-get-org-by-id-" + org.value,
64
+ () => getById(org.value),
65
+ { watch: [org] }
66
+ );
39
67
 
40
- if (orgResult.status === "fulfilled" && orgResult.value) {
41
- orgNature.value = orgResult.value.nature ?? "";
68
+ watchEffect(() => {
69
+ if (getOrgByIdReq.value) {
70
+ orgNature.value = getOrgByIdReq.value.nature ?? "";
42
71
  }
72
+ });
43
73
 
44
- if (roleResult.status === "fulfilled" && roleResult.value) {
45
- userAppRole.value = roleResult.value;
74
+ const { data: getRoleByIdReq } = await useLazyAsyncData(
75
+ "plugin-get-role-by-id-" + roleId.value,
76
+ () => getRoleById(roleId.value),
77
+ { watch: [roleId] }
78
+ );
79
+
80
+ watchEffect(() => {
81
+ if (getRoleByIdReq.value) {
82
+ userAppRole.value = getRoleByIdReq.value;
46
83
  }
47
- } catch (error) {
48
- return { name: "index" };
49
- }
84
+ });
50
85
  });
51
86
  });
@@ -42,8 +42,6 @@ import {
42
42
  } from "vuetify-pro-tiptap";
43
43
  import "vuetify-pro-tiptap/style.css";
44
44
 
45
- import { LIGHT_THEME, DARK_THEME } from "../utils/theme";
46
-
47
45
  export default defineNuxtPlugin((app) => {
48
46
  const vuetify = createVuetify({
49
47
  defaults: {
@@ -71,43 +69,17 @@ export default defineNuxtPlugin((app) => {
71
69
  // hint: "This field is required",
72
70
  // },
73
71
  },
74
- /**
75
- * THE PRODUCT HAS TWO THEMES, AND UNTIL NOW ONLY ONE OF THEM WAS OURS.
76
- *
77
- * Every app shows a light/dark toggle in the app bar (Layout/Header.vue),
78
- * and it sets Vuetify's built-in "light" and "dark" themes. Those were left
79
- * at Vuetify's factory values, so one click on any screen in any of the
80
- * eleven apps threw the brand away: `primary` stopped being the Seven365
81
- * navy and became Material blue #2196F3 - and `primary` is the navigation
82
- * drawer's fill - while `primary-button` and `text-primary`, used in around
83
- * eighty places across the layer and the apps, do not exist in a stock
84
- * theme at all, so those components lost their colour to an undefined CSS
85
- * variable. There was also no way back to the brand: the toggle only ever
86
- * flipped between the two stock themes.
87
- *
88
- * The fix is to make the stock names OURS rather than to add new names.
89
- * Several components already branch on `theme.global.name === "dark"` (this
90
- * layer's FormDialog, property-management's facility icons), so a
91
- * differently-named dark theme would have left every one of them silently
92
- * on their light branch inside a dark app. Overriding `light` and `dark`
93
- * means the toggle, `v-theme-provider theme="light"`, the `plain-dark`
94
- * layout and all of those comparisons keep working, and start being right.
95
- *
96
- * The colours themselves, and every ratio they were chosen for, are in
97
- * `utils/theme.ts`, which `utils/theme.test.ts` measures on every run.
98
- */
99
72
  theme: {
100
- defaultTheme: "light",
73
+ defaultTheme: "iservice365",
101
74
  themes: {
102
- light: LIGHT_THEME,
103
- dark: DARK_THEME,
104
- /**
105
- * The name this product booted on before the two above carried the
106
- * brand. Kept, and identical to `light`, so anything that asks for it
107
- * by name - in an app that is not in this repository - still gets a
108
- * theme rather than a blank one.
109
- */
110
- iservice365: LIGHT_THEME,
75
+ iservice365: {
76
+ dark: false,
77
+ colors: {
78
+ primary: "#042134",
79
+ "primary-button": "#1867C0",
80
+ "text-primary": "#052439",
81
+ },
82
+ },
111
83
  },
112
84
  },
113
85
  icons: {
package/types/site.d.ts CHANGED
@@ -1,76 +1,5 @@
1
1
  declare type TSiteCreate = Pick<TSite, "name" | "description" | "orgId">;
2
2
 
3
- declare type THidQrCodeFormat = "0" | "1" | "2";
4
-
5
- declare type THidPermissionCategory =
6
- | "resident"
7
- | "property_management"
8
- | "service_provider";
9
-
10
- declare type THidPermissionAssignment = {
11
- subjectId: string;
12
- category: THidPermissionCategory;
13
- name: string;
14
- subtitle?: string;
15
- intercom: boolean;
16
- readerId?: string;
17
- location?: string;
18
- locationKey?: string;
19
- };
20
-
21
- declare type THidSitePermissions = {
22
- orgId: string;
23
- site: string;
24
- readerId: string;
25
- location: string;
26
- assignments: THidPermissionAssignment[];
27
- counts: {
28
- resident: number;
29
- propertyManagement: number;
30
- serviceProvider: number;
31
- intercom: number;
32
- };
33
- };
34
-
35
- declare type THidPermissionCandidate = THidPermissionAssignment & {
36
- selected: boolean;
37
- };
38
-
39
- declare type THidQrCodePassConfig = {
40
- enabled: boolean;
41
- onlineMode?: boolean;
42
- readerId: string;
43
- qrFormat: THidQrCodeFormat;
44
- identificationMethods?: {
45
- facial: boolean;
46
- card: boolean;
47
- qrCode: boolean;
48
- idPassword: boolean;
49
- pin: boolean;
50
- bluetooth: boolean;
51
- };
52
- printer: {
53
- vendorId: string;
54
- productId: string;
55
- };
56
- template: {
57
- header: string;
58
- subtext: string;
59
- };
60
- validityMinutes: number | null;
61
- updatedAt?: string;
62
- };
63
-
64
- declare type TSiteMetadata = {
65
- block?: number;
66
- guardPosts?: number;
67
- gracePeriod?: number;
68
- incidentCounter?: number;
69
- incidentLogo?: string;
70
- services?: Record<string, unknown>[];
71
- hidQrCodePass?: THidQrCodePassConfig;
72
- };
73
-
74
3
 
75
4
  declare type TSite = {
76
5
  _id?: string;