@7365admin1/layer-common 3.2.0 → 3.2.1-staging.59

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 (33) hide show
  1. package/.changeset/camera-wall-gated-endpoint.md +47 -0
  2. package/components/CameraWall.vue +625 -0
  3. package/components/CameraWallTile.vue +333 -0
  4. package/components/EntryPassInformation.vue +4 -4
  5. package/components/HidAccessLogDashboard.vue +53 -8
  6. package/components/HidIntercomManagement.vue +842 -201
  7. package/components/HidQrCodeConfiguration.vue +907 -95
  8. package/components/HidServiceSettingsPanel.vue +20 -1
  9. package/components/HidUserEnrollment.vue +96 -2
  10. package/components/Nfc/NFCPatrolRouteMain.vue +52 -7
  11. package/components/OnlineFormConfigurationForm.vue +620 -224
  12. package/components/OnlineFormFill.vue +36 -8
  13. package/components/OnlineFormsConfiguration.vue +6 -2
  14. package/components/QrTemplate/CreditCardLandscape.vue +19 -12
  15. package/components/QrTemplate/PrintDialog.vue +209 -196
  16. package/components/SiteSettings.vue +4 -2
  17. package/components/VisitorForm.vue +201 -78
  18. package/components/VisitorManagement.vue +0 -1
  19. package/composables/useHidAmico.ts +127 -0
  20. package/composables/useHidNavigation.ts +0 -7
  21. package/composables/useOnlineFormPages.ts +2 -0
  22. package/composables/useSipWebPhone.ts +332 -0
  23. package/composables/useSiteSettings.ts +28 -0
  24. package/composables/useWebUsb.ts +127 -37
  25. package/package.json +2 -1
  26. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  27. package/plugins/secure-member.client.ts +21 -56
  28. package/types/site.d.ts +71 -0
  29. package/utils/camera-wall.test.ts +149 -0
  30. package/utils/camera-wall.ts +299 -0
  31. package/components/HidIdentityMapping.vue +0 -975
  32. package/middleware/member.ts +0 -4
  33. package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +0 -23
@@ -496,10 +496,79 @@
496
496
  </v-card-actions>
497
497
  </v-card>
498
498
  </v-dialog>
499
+
500
+ <v-dialog v-model="hidQrPreviewDialog" max-width="440" persistent>
501
+ <v-card class="hid-qr-preview-card">
502
+ <v-toolbar color="transparent" density="comfortable" class="px-2">
503
+ <v-toolbar-title class="text-subtitle-1 font-weight-bold">
504
+ Visitor QR Code
505
+ </v-toolbar-title>
506
+ <v-btn icon="mdi-close" variant="text" title="Close" @click="closeHidQrPreview" />
507
+ </v-toolbar>
508
+
509
+ <v-card-text class="px-8 pb-6 text-center">
510
+ <div class="text-h6 font-weight-bold">
511
+ {{ hidQrTemplate.header || "Visitor Access Pass" }}
512
+ </div>
513
+ <div class="text-body-2 text-medium-emphasis mt-1">
514
+ {{ hidQrTemplate.subtext || "Scan at the HID reader for entry" }}
515
+ </div>
516
+
517
+ <div class="hid-qr-image-shell my-5">
518
+ <v-img :src="hidQrPreview.imageUrl" width="280" height="280" alt="Visitor access QR code" />
519
+ </div>
520
+
521
+ <div class="text-h6 font-weight-bold">{{ visitor.name }}</div>
522
+ <div v-if="hidQrPreview.expiresAt" class="text-caption text-medium-emphasis mt-1">
523
+ Valid until {{ formatHidQrExpiry(hidQrPreview.expiresAt) }}
524
+ </div>
525
+
526
+ <v-alert v-if="hidQrPreviewError" type="error" variant="tonal" density="compact" class="mt-4 text-left">
527
+ {{ hidQrPreviewError }}
528
+ </v-alert>
529
+ </v-card-text>
530
+
531
+ <v-divider />
532
+ <v-card-actions class="pa-0">
533
+ <v-btn
534
+ variant="text"
535
+ height="52"
536
+ class="flex-grow-1 text-none"
537
+ prepend-icon="mdi-download"
538
+ @click="downloadHidQrCode"
539
+ >
540
+ Download
541
+ </v-btn>
542
+ <v-btn
543
+ v-if="hasHidQrPrinterConfig"
544
+ variant="flat"
545
+ color="primary-button"
546
+ height="52"
547
+ class="flex-grow-1 text-none"
548
+ prepend-icon="mdi-printer"
549
+ :loading="hidQrPrinting"
550
+ @click="printHidQrCodePass"
551
+ >
552
+ Print
553
+ </v-btn>
554
+ <v-btn
555
+ v-else
556
+ variant="flat"
557
+ color="primary-button"
558
+ height="52"
559
+ class="flex-grow-1 text-none"
560
+ @click="closeHidQrPreview"
561
+ >
562
+ Close
563
+ </v-btn>
564
+ </v-card-actions>
565
+ </v-card>
566
+ </v-dialog>
499
567
  </v-card>
500
568
  </template>
501
569
 
502
570
  <script setup lang="ts">
571
+ import QRCode from "qrcode";
503
572
  import { propsFactory } from "vuetify/lib/util/propsFactory.mjs";
504
573
  import useBuildingUnit from "../composables/useBuildingUnit";
505
574
  import useWebUsb from "../composables/useWebUsb";
@@ -511,6 +580,7 @@ import useVisitor from "../composables/useVisitor";
511
580
  import useSiteEntryPassSettings from "../composables/useSiteEntryPassSettings";
512
581
  import useAccessManagement from "../composables/useAccessManagement";
513
582
  import usePeople from "../composables/usePeople";
583
+ import useHidAmico from "../composables/useHidAmico";
514
584
 
515
585
  const prop = defineProps({
516
586
  type: {
@@ -562,6 +632,7 @@ const {
562
632
  const { getBySiteId: getEntryPassSettingsBySiteId } =
563
633
  useSiteEntryPassSettings();
564
634
  const { createVisitorPass, signQr } = useAccessManagement();
635
+ const { issueVisitorQr } = useHidAmico();
565
636
  const { testConnection } = useWebUsb();
566
637
  const {
567
638
  findPersonByNRIC,
@@ -610,6 +681,15 @@ const hidQrPrinted = ref(false);
610
681
  const isPhoneFieldFocused = ref(false);
611
682
  const isPlateNumberFieldFocused = ref(false);
612
683
  const isNricFieldFocused = ref(false);
684
+ const hidQrPrinting = ref(false);
685
+ const hidQrPreviewDialog = ref(false);
686
+ const hidQrPreviewError = ref("");
687
+ const hidQrPendingCompletion = ref<"done" | "done:more" | null>(null);
688
+ const hidQrPreview = reactive({
689
+ imageUrl: "",
690
+ issuedAt: "",
691
+ expiresAt: "",
692
+ });
613
693
 
614
694
  const registeredUnitCompanyName = ref("N/A");
615
695
 
@@ -710,7 +790,8 @@ const requireNRIC = computed(() => {
710
790
  });
711
791
 
712
792
  const hidQrCodePassConfig = computed(() => {
713
- return (siteData.value as any)?.metadata?.hidQrCodePass || {};
793
+ const site = siteData.value as TSite | null;
794
+ return site?.metadata?.hidQrCodePass;
714
795
  });
715
796
 
716
797
  const hidQrCodePassEnabled = computed(() => {
@@ -736,6 +817,12 @@ const hidQrValidityMinutes = computed(() => {
736
817
  return Number.isFinite(value) && value > 0 ? value : 60;
737
818
  });
738
819
 
820
+ const hidQrReaderId = computed(() => String(hidQrCodePassConfig.value?.readerId || ""));
821
+ const hidQrFormat = computed<"0" | "1" | "2">(() => {
822
+ const value = String(hidQrCodePassConfig.value?.qrFormat || "0");
823
+ return value === "1" || value === "2" ? value : "0";
824
+ });
825
+
739
826
  const hidQrCodePassAllowed = computed(() => {
740
827
  return (
741
828
  hidQrCodePassEnabled.value &&
@@ -1710,7 +1797,7 @@ async function submit(options: { skipNonCheckedOutCheck?: boolean } = {}) {
1710
1797
  return;
1711
1798
  }
1712
1799
 
1713
- if (passType.value === "HID_QR" && !validateHidQrPrinterConfig()) return;
1800
+ if (passType.value === "HID_QR" && !validateHidQrReaderConfig()) return;
1714
1801
  if (
1715
1802
  prop.mode === "add" &&
1716
1803
  !options.skipNonCheckedOutCheck &&
@@ -1800,11 +1887,10 @@ async function submit(options: { skipNonCheckedOutCheck?: boolean } = {}) {
1800
1887
 
1801
1888
  if (res) {
1802
1889
  if (passType.value === "HID_QR") {
1803
- const printed = await printHidQrCodePass(
1804
- getVisitorIdFromResponse(res),
1805
- false
1806
- );
1807
- if (!printed) return;
1890
+ const previewReady = await prepareHidQrCodePreview(getVisitorIdFromResponse(res));
1891
+ if (!previewReady) return;
1892
+ hidQrPendingCompletion.value = createMore.value ? "done:more" : "done";
1893
+ return;
1808
1894
  }
1809
1895
 
1810
1896
  if (
@@ -1837,12 +1923,9 @@ async function submit(options: { skipNonCheckedOutCheck?: boolean } = {}) {
1837
1923
  });
1838
1924
 
1839
1925
  if (passType.value === "QR") {
1840
- console.log("[QR Print] passRes:", passRes);
1841
1926
  const accessCards: any[] =
1842
1927
  (passRes as any)?.data?.accessCards ?? [];
1843
- console.log("[QR Print] accessCards:", accessCards);
1844
1928
  const printer = entryPassSettings.value?.data?.settings?.printer;
1845
- console.log("[QR Print] printer config:", printer);
1846
1929
  if (!printer?.vendorId || !printer?.productId) {
1847
1930
  console.warn("[QR Print] Aborted: no printer configured.");
1848
1931
  } else if (!accessCards.length) {
@@ -1863,31 +1946,14 @@ async function submit(options: { skipNonCheckedOutCheck?: boolean } = {}) {
1863
1946
  const address = [blockLabel, levelLabel, unitLabel]
1864
1947
  .filter(Boolean)
1865
1948
  .join("/");
1866
- console.log(
1867
- "[QR Print] Printing",
1868
- accessCards.length,
1869
- "card(s). vendorId:",
1870
- vendorId,
1871
- "productId:",
1872
- productId,
1873
- "address:",
1874
- address
1875
- );
1876
1949
 
1877
1950
  await nextTick();
1878
1951
  for (const qrCode of accessCards) {
1879
- console.log("[QR Print] Signing QR for card:", qrCode._id);
1880
1952
  const signed = await signQr({
1881
1953
  cardId: qrCode._id,
1882
1954
  purpose: "vms",
1883
1955
  });
1884
1956
  const qrData = signed?.data;
1885
- console.log(
1886
- "[QR Print] signQr result:",
1887
- signed,
1888
- "| qrData:",
1889
- qrData
1890
- );
1891
1957
  if (!qrData) {
1892
1958
  console.warn(
1893
1959
  "[QR Print] Skipping card",
@@ -1947,17 +2013,23 @@ function parseUsbId(value: unknown) {
1947
2013
  : parseInt(text, 10);
1948
2014
  }
1949
2015
 
1950
- function getVisitorIdFromResponse(response: any) {
2016
+ function getVisitorIdFromResponse(response: unknown) {
1951
2017
  if (typeof response === "string") return response;
2018
+ if (!response || typeof response !== "object") return "";
2019
+
2020
+ const result = response as Record<string, unknown>;
2021
+ const nested = result.data && typeof result.data === "object"
2022
+ ? result.data as Record<string, unknown>
2023
+ : {};
1952
2024
  return (
1953
- response?._id ||
1954
- response?.id ||
1955
- response?.data?._id ||
1956
- response?.data?.id ||
1957
- response?.insertedId ||
1958
- response?.data?.insertedId ||
2025
+ result._id ||
2026
+ result.id ||
2027
+ nested._id ||
2028
+ nested.id ||
2029
+ result.insertedId ||
2030
+ nested.insertedId ||
1959
2031
  ""
1960
- );
2032
+ ).toString();
1961
2033
  }
1962
2034
 
1963
2035
  function getHidQrPrinterUsbIds() {
@@ -1966,7 +2038,19 @@ function getHidQrPrinterUsbIds() {
1966
2038
  return { vendorId, productId };
1967
2039
  }
1968
2040
 
2041
+ function validateHidQrReaderConfig() {
2042
+ if (!hidQrReaderId.value) {
2043
+ errorMessage.value =
2044
+ "No HID reader is configured for QR Code Pass. Please select a reader in Settings.";
2045
+ return false;
2046
+ }
2047
+
2048
+ return true;
2049
+ }
2050
+
1969
2051
  function validateHidQrPrinterConfig() {
2052
+ if (!validateHidQrReaderConfig()) return false;
2053
+
1970
2054
  if (!hasHidQrPrinterConfig.value) {
1971
2055
  errorMessage.value =
1972
2056
  "No printer is configured on HID settings. Please configure the HID QR Code printer before continuing.";
@@ -1984,42 +2068,6 @@ function validateHidQrPrinterConfig() {
1984
2068
  return true;
1985
2069
  }
1986
2070
 
1987
- function buildHidQrCodePayload(visitorId = "") {
1988
- if (hidQrPrintPayload.value) return hidQrPrintPayload.value;
1989
-
1990
- const issuedAt = new Date();
1991
- const expiresAt = new Date(
1992
- issuedAt.getTime() + hidQrValidityMinutes.value * 60 * 1000
1993
- );
1994
- const nonce =
1995
- typeof crypto !== "undefined" && "randomUUID" in crypto
1996
- ? crypto.randomUUID()
1997
- : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
1998
-
1999
- hidQrPrintPayload.value = JSON.stringify({
2000
- purpose: "hid-amico-visitor-access",
2001
- visitorId,
2002
- site: prop.site,
2003
- org: prop.org,
2004
- visitorType: prop.type,
2005
- name: visitor.name || "",
2006
- company: visitor.company || "",
2007
- contact: visitor.contact || "",
2008
- nric: visitor.nric || "",
2009
- plateNumber: visitor.plateNumber || "",
2010
- block: visitor.block || "",
2011
- level: visitor.level || "",
2012
- unit: visitor.unit || "",
2013
- unitName: visitor.unitName || "",
2014
- address: buildVisitorAddress(),
2015
- issuedAt: issuedAt.toISOString(),
2016
- expiresAt: expiresAt.toISOString(),
2017
- nonce,
2018
- });
2019
-
2020
- return hidQrPrintPayload.value;
2021
- }
2022
-
2023
2071
  function buildVisitorAddress() {
2024
2072
  const blockLabel =
2025
2073
  blocksArray.value.find((b: any) => b.value === visitor.block)?.title || "";
@@ -2032,20 +2080,58 @@ function buildVisitorAddress() {
2032
2080
  return [blockLabel, levelLabel, unitLabel].filter(Boolean).join("/");
2033
2081
  }
2034
2082
 
2035
- async function printHidQrCodePass(visitorId = "", manageProcessing = true) {
2083
+ async function prepareHidQrCodePreview(visitorId: string) {
2084
+ hidQrPreviewError.value = "";
2085
+ if (!visitorId) {
2086
+ errorMessage.value = "Unable to identify the visitor for HID QR Code enrollment.";
2087
+ return false;
2088
+ }
2089
+
2090
+ try {
2091
+ const response = await issueVisitorQr(hidQrReaderId.value, {
2092
+ visitorId,
2093
+ validityMinutes: hidQrValidityMinutes.value,
2094
+ qrFormat: hidQrFormat.value,
2095
+ });
2096
+ const credential = "data" in response ? response.data : response;
2097
+ hidQrPrintPayload.value = credential.qrValue;
2098
+ if (!hidQrPrintPayload.value) {
2099
+ errorMessage.value = "HID QR Code enrollment did not return a QR value.";
2100
+ return false;
2101
+ }
2102
+
2103
+ hidQrPreview.imageUrl = await QRCode.toDataURL(hidQrPrintPayload.value, {
2104
+ width: 360,
2105
+ margin: 2,
2106
+ errorCorrectionLevel: "M",
2107
+ });
2108
+ hidQrPreview.issuedAt = credential.issuedAt || "";
2109
+ hidQrPreview.expiresAt = credential.expiresAt || "";
2110
+ hidQrPreviewDialog.value = true;
2111
+ return true;
2112
+ } catch (error: unknown) {
2113
+ errorMessage.value = errorConverter(error) || "Unable to generate HID QR Code.";
2114
+ return false;
2115
+ }
2116
+ }
2117
+
2118
+ async function printHidQrCodePass() {
2036
2119
  if (hidQrPrinted.value) return true;
2037
2120
 
2038
2121
  if (!validateHidQrPrinterConfig()) return false;
2039
2122
 
2040
2123
  const { vendorId, productId } = getHidQrPrinterUsbIds();
2041
2124
 
2042
- if (manageProcessing) processing.value = true;
2125
+ hidQrPrinting.value = true;
2126
+ hidQrPreviewError.value = "";
2043
2127
  try {
2128
+ if (!hidQrPrintPayload.value) return false;
2129
+
2044
2130
  const printResult = await testConnection(
2045
2131
  vendorId,
2046
2132
  productId,
2047
2133
  true,
2048
- buildHidQrCodePayload(visitorId),
2134
+ hidQrPrintPayload.value,
2049
2135
  null,
2050
2136
  null,
2051
2137
  visitor.company || visitor.name || "",
@@ -2055,7 +2141,7 @@ async function printHidQrCodePass(visitorId = "", manageProcessing = true) {
2055
2141
  );
2056
2142
 
2057
2143
  if (!printResult?.success || printResult?.printTest?.success === false) {
2058
- errorMessage.value =
2144
+ hidQrPreviewError.value =
2059
2145
  printResult?.printTest?.error ||
2060
2146
  printResult?.error ||
2061
2147
  "Unable to print HID QR Code. Please check the printer connection.";
@@ -2064,14 +2150,40 @@ async function printHidQrCodePass(visitorId = "", manageProcessing = true) {
2064
2150
 
2065
2151
  hidQrPrinted.value = true;
2066
2152
  return true;
2067
- } catch (error: any) {
2068
- errorMessage.value = error?.message || "Unable to print HID QR Code.";
2153
+ } catch (error: unknown) {
2154
+ hidQrPreviewError.value = errorConverter(error) || "Unable to print HID QR Code.";
2069
2155
  return false;
2070
2156
  } finally {
2071
- if (manageProcessing) processing.value = false;
2157
+ hidQrPrinting.value = false;
2072
2158
  }
2073
2159
  }
2074
2160
 
2161
+ function formatHidQrExpiry(value: string) {
2162
+ const date = new Date(value);
2163
+ return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
2164
+ }
2165
+
2166
+ function downloadHidQrCode() {
2167
+ if (!hidQrPreview.imageUrl) return;
2168
+ const safeName = String(visitor.name || "visitor")
2169
+ .trim()
2170
+ .replace(/[^a-z0-9]+/gi, "-")
2171
+ .replace(/^-|-$/g, "")
2172
+ .toLowerCase();
2173
+ const link = document.createElement("a");
2174
+ link.href = hidQrPreview.imageUrl;
2175
+ link.download = `hid-visitor-qr-${safeName || "visitor"}.png`;
2176
+ link.click();
2177
+ }
2178
+
2179
+ function closeHidQrPreview() {
2180
+ hidQrPreviewDialog.value = false;
2181
+ const completion = hidQrPendingCompletion.value;
2182
+ hidQrPendingCompletion.value = null;
2183
+ if (completion === "done:more") emit("done:more");
2184
+ else if (completion === "done") emit("done");
2185
+ }
2186
+
2075
2187
  watch(
2076
2188
  () => visitor.plateNumber,
2077
2189
  (newVal) => {
@@ -2212,4 +2324,15 @@ onMounted(() => {
2212
2324
  font-size: 0.95rem;
2213
2325
  opacity: 1;
2214
2326
  }
2327
+
2328
+ .hid-qr-preview-card {
2329
+ border-radius: 6px;
2330
+ }
2331
+
2332
+ .hid-qr-image-shell {
2333
+ display: inline-flex;
2334
+ padding: 12px;
2335
+ border: 1px solid #dce3e8;
2336
+ background: #ffffff;
2337
+ }
2215
2338
  </style>
@@ -1601,7 +1601,6 @@ const {
1601
1601
  watch(getVisitorReq, (newData: any) => {
1602
1602
  if (newData) {
1603
1603
  items.value = newData.items ?? [];
1604
- // items.value = [{ _id: "testid", name: "John Doe", type: "Contractor", company: "ABC Corp", location: "Block A, Level 1, Unit 101", contact: "12345678", plateNumber: "SGX1234A", checkIn: new Date().toISOString(), checkOut: null, status: "registered", visitorPass: [{ _id: "1", keyId: "pass1", prefixAndName: "VIP Pass" }, { _id: "2", keyId: "pass2", prefixAndName: "VIP Pass 2" }], passKeys: [{ _id: "1", keyId: "key2", prefixAndName: "Master Key" }] }];
1605
1604
  pages.value = newData.pages ?? 0;
1606
1605
  pageRange.value = newData?.pageRange ?? "-- - -- of --";
1607
1606
  }
@@ -28,6 +28,46 @@ type HidIdentityPayload = {
28
28
  metadata?: Record<string, any>;
29
29
  };
30
30
 
31
+ type HidVisitorQrData = {
32
+ visitorId: string;
33
+ readerId: string;
34
+ hidUserId: number;
35
+ qrValue: string;
36
+ qrFormat: "0" | "1" | "2";
37
+ issuedAt: string;
38
+ expiresAt: string;
39
+ };
40
+
41
+ type HidVisitorQrResponse = HidVisitorQrData | { data: HidVisitorQrData };
42
+
43
+ type HidPermissionListResponse = {
44
+ items: THidPermissionCandidate[];
45
+ page: number;
46
+ pages: number;
47
+ pageRange: string;
48
+ limit: number;
49
+ };
50
+
51
+ type HidFacialEnrollmentResult = {
52
+ user_id?: number;
53
+ success?: boolean;
54
+ scores?: Record<string, unknown>;
55
+ errors?: Array<{ code?: number; message?: string }>;
56
+ };
57
+
58
+ export type HidSipAccountData = {
59
+ orgId: string;
60
+ site: string;
61
+ userId: string;
62
+ extension: string;
63
+ username: string;
64
+ password: string;
65
+ sipAddress: string;
66
+ websocketUrl: string;
67
+ status: "active";
68
+ provisionedAt: string;
69
+ };
70
+
31
71
  export default function useHidAmico() {
32
72
  const basePath = "/api/access-management/hid";
33
73
 
@@ -78,6 +118,16 @@ export default function useHidAmico() {
78
118
  });
79
119
  }
80
120
 
121
+ function issueVisitorQr(
122
+ readerId: string,
123
+ payload: { visitorId: string; validityMinutes: number; qrFormat: "0" | "1" | "2" },
124
+ ) {
125
+ return useNuxtApp().$api<HidVisitorQrResponse>(`${basePath}/readers/${readerId}/visitor-qr`, {
126
+ method: "POST",
127
+ body: payload,
128
+ });
129
+ }
130
+
81
131
  function getReaderConfiguration(readerId: string, payload: Record<string, string[]> = {}) {
82
132
  return useNuxtApp().$api<Record<string, any>>(`${basePath}/readers/${readerId}/configuration`, {
83
133
  method: "POST",
@@ -112,6 +162,33 @@ export default function useHidAmico() {
112
162
  });
113
163
  }
114
164
 
165
+ function setUserImage(
166
+ readerId: string,
167
+ hidUserId: string | number,
168
+ image: Blob,
169
+ options: { timestamp?: number; match?: boolean } = {},
170
+ ) {
171
+ return useNuxtApp().$api<{ data: HidFacialEnrollmentResult }>(
172
+ `${basePath}/readers/${readerId}/users/${hidUserId}/image`,
173
+ {
174
+ method: "PUT",
175
+ query: {
176
+ ...(options.timestamp ? { timestamp: options.timestamp } : {}),
177
+ match: options.match ?? true,
178
+ },
179
+ headers: { "Content-Type": "application/octet-stream" },
180
+ body: image,
181
+ },
182
+ );
183
+ }
184
+
185
+ function deleteUserImage(readerId: string, hidUserId: string | number) {
186
+ return useNuxtApp().$api<Record<string, unknown>>(
187
+ `${basePath}/readers/${readerId}/users/${hidUserId}/image`,
188
+ { method: "DELETE" },
189
+ );
190
+ }
191
+
115
192
  function createIdentity(readerId: string, payload: HidIdentityPayload) {
116
193
  return useNuxtApp().$api<Record<string, any>>(`${basePath}/readers/${readerId}/identities`, {
117
194
  method: "POST",
@@ -151,6 +228,49 @@ export default function useHidAmico() {
151
228
  });
152
229
  }
153
230
 
231
+ function getSitePermissions(siteId: string, orgId: string, readerId: string) {
232
+ return useNuxtApp().$api<{ data: THidSitePermissions }>(
233
+ `${basePath}/sites/${siteId}/permissions`,
234
+ { method: "GET", query: { orgId, readerId } },
235
+ );
236
+ }
237
+
238
+ function ensureSipAccount(siteId: string, orgId: string) {
239
+ return useNuxtApp().$api<{ data: HidSipAccountData }>(
240
+ `${basePath}/sites/${siteId}/intercom/sip-account`,
241
+ { method: "POST", body: { orgId } },
242
+ );
243
+ }
244
+
245
+ function getPermissionCandidates(
246
+ siteId: string,
247
+ query: {
248
+ orgId: string;
249
+ readerId: string;
250
+ category: THidPermissionCategory | "intercom";
251
+ search?: string;
252
+ page?: number;
253
+ limit?: number;
254
+ },
255
+ ) {
256
+ return useNuxtApp().$api<HidPermissionListResponse>(
257
+ `${basePath}/sites/${siteId}/permission-candidates`,
258
+ { method: "GET", query },
259
+ );
260
+ }
261
+
262
+ function updateSitePermissions(
263
+ siteId: string,
264
+ orgId: string,
265
+ readerId: string,
266
+ assignments: Array<Pick<THidPermissionAssignment, "subjectId" | "category" | "intercom">>,
267
+ ) {
268
+ return useNuxtApp().$api<{ data: THidSitePermissions; message: string }>(
269
+ `${basePath}/sites/${siteId}/permissions`,
270
+ { method: "PUT", body: { orgId, readerId, assignments } },
271
+ );
272
+ }
273
+
154
274
  return {
155
275
  getReaders,
156
276
  createReader,
@@ -159,10 +279,13 @@ export default function useHidAmico() {
159
279
  testReader,
160
280
  syncReader,
161
281
  runObjectOperation,
282
+ issueVisitorQr,
162
283
  getReaderConfiguration,
163
284
  setReaderConfiguration,
164
285
  getLogs,
165
286
  getUserImage,
287
+ setUserImage,
288
+ deleteUserImage,
166
289
  getIdentities,
167
290
  createIdentity,
168
291
  updateIdentity,
@@ -170,5 +293,9 @@ export default function useHidAmico() {
170
293
  getIntercomStatus,
171
294
  makeIntercomCall,
172
295
  finalizeIntercomCall,
296
+ ensureSipAccount,
297
+ getSitePermissions,
298
+ getPermissionCandidates,
299
+ updateSitePermissions,
173
300
  };
174
301
  }
@@ -41,13 +41,6 @@ export default function useHidNavigation() {
41
41
  params: { org, site },
42
42
  },
43
43
  },
44
- {
45
- title: "Identity Mapping",
46
- route: {
47
- name: "org-site-access-mgmt-identity-mapping",
48
- params: { org, site },
49
- },
50
- },
51
44
  {
52
45
  title: "Access Logs",
53
46
  route: {
@@ -20,6 +20,8 @@ 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[] }
23
25
  | { type: "divider" }
24
26
  | { type: "signature-row"; label: string; key: string }
25
27
  | { type: "acknowledgement"; text: string }