@7365admin1/layer-common 3.2.1 → 3.2.2-staging.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/camera-wall-gated-endpoint.md +47 -0
- package/.changeset/camera-wall-plain-english.md +60 -0
- package/.changeset/camera-wall-selection-and-guidance.md +65 -0
- package/.changeset/dark-primary-contrast.md +39 -0
- package/.changeset/dashboard-dark-theme-contrast.md +15 -0
- package/.changeset/service-provider-invitation-actions.md +28 -0
- package/components/CameraWall.vue +1034 -0
- package/components/CameraWallTile.vue +818 -0
- package/components/DashboardMain.vue +84 -88
- package/components/DashboardPanel.vue +1 -1
- package/components/EntryPassInformation.vue +4 -4
- package/components/FeedbackMain.vue +7 -7
- package/components/HidAccessLogDashboard.vue +119 -44
- package/components/HidCameraCaptureDialog.vue +199 -0
- package/components/HidIntercomManagement.vue +808 -201
- package/components/HidQrCodeConfiguration.vue +907 -95
- package/components/HidServiceSettingsPanel.vue +20 -1
- package/components/HidUserEnrollment.vue +392 -81
- package/components/Layout/NavigationDrawer.vue +43 -2
- package/components/NavigationItem.vue +9 -0
- package/components/Nfc/NFCPatrolRouteMain.vue +52 -7
- package/components/OnlineFormConfigurationForm.vue +620 -224
- package/components/OnlineFormFill.vue +36 -8
- package/components/OnlineFormsConfiguration.vue +6 -2
- package/components/QrTemplate/CreditCardLandscape.vue +19 -12
- package/components/QrTemplate/PrintDialog.vue +209 -196
- package/components/ServiceProviderInvitationPrompt.vue +150 -0
- package/components/ServiceProviderMain.vue +258 -14
- package/components/SiteSettings.vue +4 -2
- package/components/VisitorForm.vue +201 -78
- package/components/VisitorManagement.vue +2 -3
- package/components/VisitorSocketPopUp.vue +56 -2
- package/composables/useComment.ts +3 -3
- package/composables/useHidAmico.ts +127 -0
- package/composables/useHidNavigation.ts +0 -7
- package/composables/useLocalAuth.ts +9 -36
- package/composables/useOnlineFormPages.ts +2 -0
- package/composables/useServiceProviderInvitation.ts +145 -0
- package/composables/useSipWebPhone.ts +311 -0
- package/composables/useSiteSettings.ts +50 -0
- package/composables/useVisitorSocket.ts +26 -0
- package/composables/useWebUsb.ts +127 -37
- package/package.json +3 -1
- package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
- package/plugins/secure-member.client.ts +21 -56
- package/plugins/vuetify.ts +37 -9
- package/test/visitor-socket.test.mjs +36 -0
- package/types/site.d.ts +71 -0
- package/utils/camera-wall.test.ts +571 -0
- package/utils/camera-wall.ts +926 -0
- package/utils/theme.test.ts +216 -0
- package/utils/theme.ts +163 -0
- package/components/HidIdentityMapping.vue +0 -975
- package/middleware/member.ts +0 -4
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +0 -23
|
@@ -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: {
|
|
@@ -1,36 +1,7 @@
|
|
|
1
1
|
export default function useLocalAuth() {
|
|
2
2
|
const { cookieConfig } = useRuntimeConfig().public;
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
country: "",
|
|
6
|
-
address1: "",
|
|
7
|
-
address2: "",
|
|
8
|
-
city: "",
|
|
9
|
-
province: "",
|
|
10
|
-
postalCode: "",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const user = {
|
|
14
|
-
_id: "",
|
|
15
|
-
givenName: "",
|
|
16
|
-
middleName: "",
|
|
17
|
-
surname: "",
|
|
18
|
-
address: _address,
|
|
19
|
-
email: "",
|
|
20
|
-
password: "",
|
|
21
|
-
type: "",
|
|
22
|
-
createdAt: "",
|
|
23
|
-
organization: "",
|
|
24
|
-
role: "",
|
|
25
|
-
primaryPhone: "",
|
|
26
|
-
mobilePhone: "",
|
|
27
|
-
serviceProviders: [],
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const currentUser = useState(
|
|
31
|
-
"currentUser",
|
|
32
|
-
(): TUser | null => JSON.parse(JSON.stringify(user))
|
|
33
|
-
);
|
|
4
|
+
const currentUser = useState<TUser | null>("currentUser", () => null);
|
|
34
5
|
|
|
35
6
|
async function authenticate() {
|
|
36
7
|
const user = useCookie("user", cookieConfig).value;
|
|
@@ -41,9 +12,7 @@ export default function useLocalAuth() {
|
|
|
41
12
|
);
|
|
42
13
|
|
|
43
14
|
watchEffect(() => {
|
|
44
|
-
|
|
45
|
-
currentUser.value = getCurrentUserReq.value as TUser;
|
|
46
|
-
}
|
|
15
|
+
currentUser.value = (getCurrentUserReq.value as TUser | null) ?? null;
|
|
47
16
|
});
|
|
48
17
|
|
|
49
18
|
watchEffect(() => {
|
|
@@ -70,6 +39,7 @@ export default function useLocalAuth() {
|
|
|
70
39
|
useCookie("sid", cookieConfig).value = null;
|
|
71
40
|
useCookie("user", cookieConfig).value = null;
|
|
72
41
|
useCookie("organization", cookieConfig).value = null;
|
|
42
|
+
currentUser.value = null;
|
|
73
43
|
}
|
|
74
44
|
|
|
75
45
|
async function logout() {
|
|
@@ -79,17 +49,20 @@ export default function useLocalAuth() {
|
|
|
79
49
|
await useNuxtApp().$api(`/api/auth/${sid}`, {
|
|
80
50
|
method: "DELETE",
|
|
81
51
|
});
|
|
82
|
-
|
|
83
|
-
clearCookies();
|
|
84
52
|
} catch (error) {
|
|
85
53
|
console.error("Logout failed:", error);
|
|
86
54
|
}
|
|
55
|
+
|
|
56
|
+
clearCookies();
|
|
87
57
|
}
|
|
88
58
|
}
|
|
89
59
|
|
|
90
60
|
async function getCurrentUser() {
|
|
91
61
|
const user = useCookie("user", cookieConfig).value;
|
|
92
|
-
if (!user)
|
|
62
|
+
if (!user) {
|
|
63
|
+
currentUser.value = null;
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
93
66
|
const _user = await useNuxtApp().$api<TUser>(`/api/users/id/${user}`, {
|
|
94
67
|
method: "GET",
|
|
95
68
|
});
|
|
@@ -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 }
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service-provider invitations — the list a property manager sees and the
|
|
3
|
+
* things they can do to a row.
|
|
4
|
+
*
|
|
5
|
+
* Separate from `useVerification` because these rows are not generic
|
|
6
|
+
* verifications any more: they have their own states (waiting for Seven365,
|
|
7
|
+
* waiting for the provider, not approved, declined) and their own actions,
|
|
8
|
+
* and the server scopes them to the caller's own organisation.
|
|
9
|
+
*/
|
|
10
|
+
export type TServiceProviderInvitation = {
|
|
11
|
+
_id: string;
|
|
12
|
+
email: string;
|
|
13
|
+
type: string;
|
|
14
|
+
status: string;
|
|
15
|
+
statusLabel: string;
|
|
16
|
+
rejectionReason?: string | null;
|
|
17
|
+
createdAt?: string;
|
|
18
|
+
expireAt?: string;
|
|
19
|
+
deletedAt?: string | null;
|
|
20
|
+
metadata?: Record<string, any>;
|
|
21
|
+
history?: Array<{
|
|
22
|
+
action: string;
|
|
23
|
+
at: string;
|
|
24
|
+
byName?: string;
|
|
25
|
+
reason?: string;
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** The words on screen. Kept beside the server's own list on purpose — a
|
|
30
|
+
* status the server invents must never render as a raw stored value. */
|
|
31
|
+
export const SERVICE_PROVIDER_INVITE_LABELS: Record<string, string> = {
|
|
32
|
+
"awaiting-approval": "Waiting for Seven365",
|
|
33
|
+
pending: "Waiting for the provider",
|
|
34
|
+
rejected: "Not approved",
|
|
35
|
+
complete: "Accepted",
|
|
36
|
+
declined: "Declined",
|
|
37
|
+
cancelled: "Cancelled",
|
|
38
|
+
expired: "Expired",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function serviceProviderInviteLabel(status?: string) {
|
|
42
|
+
return SERVICE_PROVIDER_INVITE_LABELS[String(status ?? "")] ?? "Unknown";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default function useServiceProviderInvitation() {
|
|
46
|
+
const base = "/api/service-provider-invitations";
|
|
47
|
+
|
|
48
|
+
function getInvitations({
|
|
49
|
+
org = "",
|
|
50
|
+
status = "",
|
|
51
|
+
page = 1,
|
|
52
|
+
limit = 20,
|
|
53
|
+
} = {}): Promise<{
|
|
54
|
+
items: TServiceProviderInvitation[];
|
|
55
|
+
pages: number;
|
|
56
|
+
pageRange: string;
|
|
57
|
+
}> {
|
|
58
|
+
return useNuxtApp().$api(base, {
|
|
59
|
+
method: "GET",
|
|
60
|
+
query: { org, status, page, limit },
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function cancelInvitation(id: string) {
|
|
65
|
+
return useNuxtApp().$api<{ message: string }>(`${base}/${id}/cancel`, {
|
|
66
|
+
method: "PUT",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Sends the same invitation again. Never creates a second row. */
|
|
71
|
+
function resendInvitation(id: string) {
|
|
72
|
+
return useNuxtApp().$api<{ message: string }>(`${base}/${id}/resend`, {
|
|
73
|
+
method: "PUT",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Soft delete — the row leaves this list, Seven365 keeps the record. */
|
|
78
|
+
function deleteInvitation(id: string) {
|
|
79
|
+
return useNuxtApp().$api<{ message: string }>(`${base}/${id}`, {
|
|
80
|
+
method: "DELETE",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---- Seven365 super admin ------------------------------------------------
|
|
85
|
+
|
|
86
|
+
function getApprovals({
|
|
87
|
+
status = "awaiting-approval",
|
|
88
|
+
page = 1,
|
|
89
|
+
limit = 20,
|
|
90
|
+
includeDeleted = false,
|
|
91
|
+
} = {}): Promise<{
|
|
92
|
+
items: TServiceProviderInvitation[];
|
|
93
|
+
pages: number;
|
|
94
|
+
pageRange: string;
|
|
95
|
+
awaitingApprovalCount: number;
|
|
96
|
+
}> {
|
|
97
|
+
return useNuxtApp().$api(`${base}/approvals`, {
|
|
98
|
+
method: "GET",
|
|
99
|
+
query: { status, page, limit, includeDeleted },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getApprovalCount(): Promise<{ count: number }> {
|
|
104
|
+
return useNuxtApp().$api(`${base}/approvals/count`, { method: "GET" });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function approveInvitation(id: string) {
|
|
108
|
+
return useNuxtApp().$api<{ message: string }>(`${base}/${id}/approve`, {
|
|
109
|
+
method: "PUT",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The reason is required — it is what the property manager is shown. */
|
|
114
|
+
function rejectInvitation(id: string, reason: string) {
|
|
115
|
+
return useNuxtApp().$api<{ message: string }>(`${base}/${id}/reject`, {
|
|
116
|
+
method: "PUT",
|
|
117
|
+
body: { reason },
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---- the invited service provider ----------------------------------------
|
|
122
|
+
|
|
123
|
+
function getInvitation(id: string): Promise<TServiceProviderInvitation> {
|
|
124
|
+
return useNuxtApp().$api(`${base}/${id}`, { method: "GET" });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function declineInvitation(id: string) {
|
|
128
|
+
return useNuxtApp().$api<{ message: string }>(`${base}/${id}/decline`, {
|
|
129
|
+
method: "PUT",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
getInvitations,
|
|
135
|
+
cancelInvitation,
|
|
136
|
+
resendInvitation,
|
|
137
|
+
deleteInvitation,
|
|
138
|
+
getApprovals,
|
|
139
|
+
getApprovalCount,
|
|
140
|
+
approveInvitation,
|
|
141
|
+
rejectInvitation,
|
|
142
|
+
getInvitation,
|
|
143
|
+
declineInvitation,
|
|
144
|
+
};
|
|
145
|
+
}
|