@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
|
@@ -12,75 +12,40 @@ export default defineNuxtPlugin(() => {
|
|
|
12
12
|
|
|
13
13
|
const { userAppRole, id, orgNature } = useLocalSetup();
|
|
14
14
|
|
|
15
|
-
router.
|
|
15
|
+
router.beforeEach(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
|
-
()
|
|
23
|
-
(to.params.org as string) || (to.params.organization as string) || ""
|
|
24
|
-
);
|
|
21
|
+
const org =
|
|
22
|
+
(to.params.org as string) || (to.params.organization as string) || "";
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (!hexSchema.safeParse(org.value).success) {
|
|
29
|
-
return router.replace({ name: "require-organization-membership" });
|
|
24
|
+
if (!hexSchema.safeParse(org).success) {
|
|
25
|
+
return { name: "require-organization-membership" };
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
const userId =
|
|
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");
|
|
28
|
+
const userId = useCookie("user").value ?? "";
|
|
29
|
+
if (!userId) return { name: "index" };
|
|
54
30
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
roleId.value = userMemberData.value.role ?? "roleId";
|
|
59
|
-
}
|
|
60
|
-
});
|
|
31
|
+
try {
|
|
32
|
+
const userMemberData = await getByUserType(userId, APP, org);
|
|
33
|
+
id.value = userMemberData.org ?? "";
|
|
61
34
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
);
|
|
35
|
+
const [orgResult, roleResult] = await Promise.allSettled([
|
|
36
|
+
getById(org),
|
|
37
|
+
userMemberData.role ? getRoleById(userMemberData.role) : null,
|
|
38
|
+
]);
|
|
67
39
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
orgNature.value = getOrgByIdReq.value.nature ?? "";
|
|
40
|
+
if (orgResult.status === "fulfilled" && orgResult.value) {
|
|
41
|
+
orgNature.value = orgResult.value.nature ?? "";
|
|
71
42
|
}
|
|
72
|
-
});
|
|
73
43
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
() => getRoleById(roleId.value),
|
|
77
|
-
{ watch: [roleId] }
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
watchEffect(() => {
|
|
81
|
-
if (getRoleByIdReq.value) {
|
|
82
|
-
userAppRole.value = getRoleByIdReq.value;
|
|
44
|
+
if (roleResult.status === "fulfilled" && roleResult.value) {
|
|
45
|
+
userAppRole.value = roleResult.value;
|
|
83
46
|
}
|
|
84
|
-
})
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return { name: "index" };
|
|
49
|
+
}
|
|
85
50
|
});
|
|
86
51
|
});
|
package/plugins/vuetify.ts
CHANGED
|
@@ -42,6 +42,8 @@ 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
|
+
|
|
45
47
|
export default defineNuxtPlugin((app) => {
|
|
46
48
|
const vuetify = createVuetify({
|
|
47
49
|
defaults: {
|
|
@@ -69,17 +71,43 @@ export default defineNuxtPlugin((app) => {
|
|
|
69
71
|
// hint: "This field is required",
|
|
70
72
|
// },
|
|
71
73
|
},
|
|
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
|
+
*/
|
|
72
99
|
theme: {
|
|
73
|
-
defaultTheme: "
|
|
100
|
+
defaultTheme: "light",
|
|
74
101
|
themes: {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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,
|
|
83
111
|
},
|
|
84
112
|
},
|
|
85
113
|
icons: {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
|
|
4
|
+
import { cameraAlertMessage } from "./.build/useVisitorSocket.mjs";
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* The reported symptom was one camera raising a red toast every 10 seconds on
|
|
8
|
+
* an unrelated page. API-core now paces the alert; this side has to make sure
|
|
9
|
+
* several cameras failing at once still cannot become several snackbars.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
test("nothing is shown when no camera is down", () => {
|
|
13
|
+
assert.equal(cameraAlertMessage([]), "");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("one failing camera shows the server's own message, which names it", () => {
|
|
17
|
+
const text = "The entry and exit ANPR camera at Seventh Condominium is not responding.";
|
|
18
|
+
assert.equal(cameraAlertMessage([text]), text);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("several failing cameras collapse into a single line", () => {
|
|
22
|
+
const many = ["camera one is down", "camera two is down", "camera three is down"];
|
|
23
|
+
const text = cameraAlertMessage(many);
|
|
24
|
+
assert.match(text, /^3 cameras at this site are not responding/);
|
|
25
|
+
for (const message of many) assert.doesNotMatch(text, new RegExp(message));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("the collapsed line points at the page that lists them", () => {
|
|
29
|
+
assert.match(cameraAlertMessage(["a", "b"]), /Site Settings > Cameras/);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("no camera message tells the operator to deactivate anything", () => {
|
|
33
|
+
for (const messages of [["a camera is down"], ["a", "b"]]) {
|
|
34
|
+
assert.doesNotMatch(cameraAlertMessage(messages), /inactive|deactivat/i);
|
|
35
|
+
}
|
|
36
|
+
});
|
package/types/site.d.ts
CHANGED
|
@@ -1,5 +1,76 @@
|
|
|
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
|
+
|
|
3
74
|
|
|
4
75
|
declare type TSite = {
|
|
5
76
|
_id?: string;
|