@7365admin1/layer-common 4.0.4-staging.241 → 4.1.0
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/CHANGELOG.md +14 -4
- package/components/EntryPass/SelfServiceQrPreview.vue +79 -0
- package/components/EntryPassMain.vue +289 -0
- package/components/PublicOnboarding/ContractorRegistrationForm.vue +278 -0
- package/components/PublicOnboarding/LocationFields.vue +125 -0
- package/components/PublicOnboarding/TypeSelectCard.vue +34 -0
- package/components/PublicOnboarding/VisitorRegistrationForm.vue +273 -0
- package/components/VisitorManagement.vue +205 -14
- package/composables/useContactPicker.ts +36 -0
- package/composables/usePublicVisitorOnboarding.ts +73 -0
- package/composables/useWebUsb.ts +64 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 4.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a45deb4: Add a canonical permission catalogue and a test that fails when the UI and the server stop agreeing
|
|
8
|
+
|
|
9
|
+
`constants/permissions.ts` exports the canonical spelling of every permission the estate uses, typed so a misspelt string is a build error rather than a check that is silently false forever, plus a map from each legacy spelling (four for work orders, two each for visitors, bulletin boards, buildings and service providers) to its canonical form.
|
|
10
|
+
|
|
11
|
+
Additive only: no gate is rewired and no existing check changes the string it asks for. `test/permission-vocabulary.test.mjs` fails when a new string appears in this package, or when the server's enforced set — snapshotted in `test/fixtures/server-permissions.json` by `tools/refresh-server-permissions.mjs` — contains something the catalogue has no name for.
|
|
12
|
+
|
|
3
13
|
## 4.0.3
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
|
@@ -280,11 +290,11 @@
|
|
|
280
290
|
`utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
|
|
281
291
|
own rule from two endpoints the console already calls, unprojected:
|
|
282
292
|
|
|
283
|
-
|
|
284
|
-
|
|
293
|
+
GET /api/members/user/:user/app/admin the Seven365 staff membership
|
|
294
|
+
GET /api/roles/id/:role that membership's role document
|
|
285
295
|
|
|
286
|
-
|
|
287
|
-
|
|
296
|
+
owner = member.type === "admin" && role.type === "admin" && role.default === true
|
|
297
|
+
staff = member.type === "admin" && role.type === "admin"
|
|
288
298
|
|
|
289
299
|
`role.default` is the marker because it is the only property of a platform
|
|
290
300
|
staff role no API caller can set - `role.controller.ts` validates create and
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-dialog
|
|
3
|
+
:model-value="modelValue"
|
|
4
|
+
transition="dialog-right-transition"
|
|
5
|
+
width="420"
|
|
6
|
+
persistent
|
|
7
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
8
|
+
>
|
|
9
|
+
<v-card class="screen-modal">
|
|
10
|
+
<div class="d-flex justify-end pa-2">
|
|
11
|
+
<v-btn
|
|
12
|
+
variant="text"
|
|
13
|
+
icon="mdi-close"
|
|
14
|
+
class="screen-modal__close"
|
|
15
|
+
aria-label="Close preview"
|
|
16
|
+
@click="emit('update:modelValue', false)"
|
|
17
|
+
></v-btn>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<v-card-text class="screen-modal__body pa-0 pb-4">
|
|
21
|
+
<!-- Matches the printed A5 layout: one QR, a "scan" subtext, and a
|
|
22
|
+
dark banner carrying the site name - not the multi-copy receipt
|
|
23
|
+
`EntryPassQrTemplatePreview` renders for physical passes. -->
|
|
24
|
+
<div class="onboarding-qr-preview mx-6">
|
|
25
|
+
<img v-if="qrImage" :src="qrImage" width="240" height="240" class="d-block mx-auto" />
|
|
26
|
+
<p class="onboarding-qr-preview__subtext text-center mt-4">
|
|
27
|
+
{{ (subText || "SCAN QR CODE").toUpperCase() }}
|
|
28
|
+
</p>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="onboarding-qr-preview__banner text-center mt-4 py-4">
|
|
31
|
+
{{ (header || "").toUpperCase() }}
|
|
32
|
+
</div>
|
|
33
|
+
</v-card-text>
|
|
34
|
+
</v-card>
|
|
35
|
+
</v-dialog>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<script lang="ts" setup>
|
|
39
|
+
import QRCode from "qrcode";
|
|
40
|
+
|
|
41
|
+
const props = defineProps({
|
|
42
|
+
modelValue: { type: Boolean, default: false },
|
|
43
|
+
header: { type: String, default: "" },
|
|
44
|
+
subText: { type: String, default: "" },
|
|
45
|
+
qrValue: { type: String, default: "" },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
49
|
+
|
|
50
|
+
const qrImage = ref<string>("");
|
|
51
|
+
|
|
52
|
+
async function renderQr() {
|
|
53
|
+
if (!props.qrValue) {
|
|
54
|
+
qrImage.value = "";
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
qrImage.value = await QRCode.toDataURL(props.qrValue, { width: 240, margin: 1 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
watch(() => props.qrValue, renderQr);
|
|
61
|
+
onMounted(renderQr);
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<style scoped>
|
|
65
|
+
.onboarding-qr-preview__subtext {
|
|
66
|
+
font-size: var(--fs-card-title);
|
|
67
|
+
font-weight: var(--fw-card-title);
|
|
68
|
+
color: var(--text);
|
|
69
|
+
letter-spacing: 0.5px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.onboarding-qr-preview__banner {
|
|
73
|
+
background: #000;
|
|
74
|
+
color: #fff;
|
|
75
|
+
font-size: 22px;
|
|
76
|
+
font-weight: 700;
|
|
77
|
+
letter-spacing: 2px;
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
@@ -391,6 +391,157 @@
|
|
|
391
391
|
|
|
392
392
|
</v-row>
|
|
393
393
|
</v-window-item>
|
|
394
|
+
|
|
395
|
+
<!-- ─── Tab 5: Self-Service ───────────────────────────── -->
|
|
396
|
+
<v-window-item value="onboarding">
|
|
397
|
+
<v-row no-gutters class="px-6 pt-4">
|
|
398
|
+
|
|
399
|
+
<!-- Self-Service Auto-Approvals -->
|
|
400
|
+
<v-col cols="12">
|
|
401
|
+
<div class="d-flex mt-3 pb-5 pr-5">
|
|
402
|
+
<p class="mr-6 font-weight-medium flex-grow-1">
|
|
403
|
+
Self-Service Auto-Approvals
|
|
404
|
+
<span class="d-block text-caption screen-hint mt-1">
|
|
405
|
+
Automatically issue active passes the moment a contractor or
|
|
406
|
+
visitor submits their registration form via the self-service
|
|
407
|
+
web portal.
|
|
408
|
+
</span>
|
|
409
|
+
</p>
|
|
410
|
+
<v-switch
|
|
411
|
+
color="success"
|
|
412
|
+
hide-details
|
|
413
|
+
v-model="currentEntryPassSettings.onboardingEnabled"
|
|
414
|
+
@update:modelValue="updateOnboardingEnabled()"
|
|
415
|
+
/>
|
|
416
|
+
</div>
|
|
417
|
+
</v-col>
|
|
418
|
+
|
|
419
|
+
<v-divider />
|
|
420
|
+
|
|
421
|
+
<!-- Printer Setup -->
|
|
422
|
+
<v-col cols="12">
|
|
423
|
+
<div class="mt-5 pb-5">
|
|
424
|
+
<p class="font-weight-medium mb-3">Printer Setup</p>
|
|
425
|
+
<v-row align="center">
|
|
426
|
+
<v-col cols="12" md="3">
|
|
427
|
+
<v-text-field
|
|
428
|
+
label="Vendor ID"
|
|
429
|
+
hide-details
|
|
430
|
+
clearable
|
|
431
|
+
v-model="currentEntryPassSettings.onboardingPrinterVendorId"
|
|
432
|
+
/>
|
|
433
|
+
</v-col>
|
|
434
|
+
<v-col cols="12" md="3">
|
|
435
|
+
<v-text-field
|
|
436
|
+
label="Product ID"
|
|
437
|
+
hide-details
|
|
438
|
+
clearable
|
|
439
|
+
v-model="currentEntryPassSettings.onboardingPrinterProductId"
|
|
440
|
+
/>
|
|
441
|
+
</v-col>
|
|
442
|
+
<v-col cols="12" md="3" class="d-flex align-end">
|
|
443
|
+
<v-btn
|
|
444
|
+
color="primary"
|
|
445
|
+
variant="outlined"
|
|
446
|
+
prepend-icon="mdi-magnify"
|
|
447
|
+
block
|
|
448
|
+
height="48"
|
|
449
|
+
:loading="scanningOnboardingPrinter"
|
|
450
|
+
@click="scanOnboardingPrinter()"
|
|
451
|
+
>
|
|
452
|
+
Scan Printer
|
|
453
|
+
</v-btn>
|
|
454
|
+
</v-col>
|
|
455
|
+
<v-col cols="12" md="3" class="d-flex align-end pr-0">
|
|
456
|
+
<v-btn
|
|
457
|
+
color="success"
|
|
458
|
+
variant="outlined"
|
|
459
|
+
prepend-icon="mdi-printer-check"
|
|
460
|
+
block
|
|
461
|
+
height="48"
|
|
462
|
+
:loading="testingOnboardingPrinter"
|
|
463
|
+
@click="testAndPrintOnboarding()"
|
|
464
|
+
>
|
|
465
|
+
Test & Print
|
|
466
|
+
</v-btn>
|
|
467
|
+
</v-col>
|
|
468
|
+
</v-row>
|
|
469
|
+
<v-row class="mt-4">
|
|
470
|
+
<v-col cols="12" md="3">
|
|
471
|
+
<v-btn
|
|
472
|
+
color="primary"
|
|
473
|
+
variant="flat"
|
|
474
|
+
prepend-icon="mdi-content-save"
|
|
475
|
+
block
|
|
476
|
+
@click="saveOnboardingSettings()"
|
|
477
|
+
>
|
|
478
|
+
Save Settings
|
|
479
|
+
</v-btn>
|
|
480
|
+
</v-col>
|
|
481
|
+
</v-row>
|
|
482
|
+
</div>
|
|
483
|
+
</v-col>
|
|
484
|
+
|
|
485
|
+
<v-divider />
|
|
486
|
+
|
|
487
|
+
<!-- Self-Service Pass QR Code -->
|
|
488
|
+
<v-col cols="12">
|
|
489
|
+
<div class="mt-5 pb-5">
|
|
490
|
+
<p class="font-weight-medium mb-3">Self-Service Pass QR Code</p>
|
|
491
|
+
<v-row no-gutters>
|
|
492
|
+
<v-col cols="12" md="6" class="pr-md-2">
|
|
493
|
+
<v-text-field
|
|
494
|
+
label="Header"
|
|
495
|
+
placeholder="Enter QR code header"
|
|
496
|
+
hide-details
|
|
497
|
+
density="comfortable"
|
|
498
|
+
clearable
|
|
499
|
+
v-model="currentEntryPassSettings.onboardingHeader"
|
|
500
|
+
/>
|
|
501
|
+
</v-col>
|
|
502
|
+
<v-col cols="12" md="6" class="pl-md-2 mt-3 mt-md-0">
|
|
503
|
+
<v-text-field
|
|
504
|
+
label="Subtext"
|
|
505
|
+
placeholder="Enter QR code subtext"
|
|
506
|
+
hide-details
|
|
507
|
+
density="comfortable"
|
|
508
|
+
clearable
|
|
509
|
+
v-model="currentEntryPassSettings.onboardingSubText"
|
|
510
|
+
/>
|
|
511
|
+
</v-col>
|
|
512
|
+
<v-col cols="12">
|
|
513
|
+
<v-row class="mt-3">
|
|
514
|
+
<v-col cols="12" md="4">
|
|
515
|
+
<v-btn
|
|
516
|
+
color="success"
|
|
517
|
+
variant="flat"
|
|
518
|
+
prepend-icon="mdi-eye"
|
|
519
|
+
block
|
|
520
|
+
@click="isShowOnboardingPreview = true"
|
|
521
|
+
>
|
|
522
|
+
Preview
|
|
523
|
+
</v-btn>
|
|
524
|
+
</v-col>
|
|
525
|
+
<v-col cols="12" md="4">
|
|
526
|
+
<v-btn
|
|
527
|
+
color="primary"
|
|
528
|
+
variant="flat"
|
|
529
|
+
prepend-icon="mdi-printer"
|
|
530
|
+
block
|
|
531
|
+
:loading="printingOnboardingQr"
|
|
532
|
+
@click="printOnboardingQrCode()"
|
|
533
|
+
>
|
|
534
|
+
Print QR Code
|
|
535
|
+
</v-btn>
|
|
536
|
+
</v-col>
|
|
537
|
+
</v-row>
|
|
538
|
+
</v-col>
|
|
539
|
+
</v-row>
|
|
540
|
+
</div>
|
|
541
|
+
</v-col>
|
|
542
|
+
|
|
543
|
+
</v-row>
|
|
544
|
+
</v-window-item>
|
|
394
545
|
</v-window>
|
|
395
546
|
</AppCard>
|
|
396
547
|
|
|
@@ -401,6 +552,13 @@
|
|
|
401
552
|
:header="currentEntryPassSettings.qrCodeTemplateHeader"
|
|
402
553
|
:sub-text="currentEntryPassSettings.qrCodeTemplateHeaderSubText"
|
|
403
554
|
/>
|
|
555
|
+
|
|
556
|
+
<EntryPassSelfServiceQrPreview
|
|
557
|
+
v-model="isShowOnboardingPreview"
|
|
558
|
+
:header="currentEntryPassSettings.onboardingHeader"
|
|
559
|
+
:sub-text="currentEntryPassSettings.onboardingSubText"
|
|
560
|
+
:qr-value="onboardingQrValue"
|
|
561
|
+
/>
|
|
404
562
|
</div>
|
|
405
563
|
</template>
|
|
406
564
|
|
|
@@ -420,6 +578,7 @@ const entryPassTabs = [
|
|
|
420
578
|
{ value: "qrcode", label: "QR Code", icon: "mdi-qrcode" },
|
|
421
579
|
{ value: "printer", label: "Printer", icon: "mdi-printer" },
|
|
422
580
|
{ value: "entrypass", label: "Entrypass", icon: "mdi-link-variant" },
|
|
581
|
+
{ value: "onboarding", label: "Self-Service", icon: "mdi-account-multiple-check-outline" },
|
|
423
582
|
];
|
|
424
583
|
|
|
425
584
|
const currentEntryPassSettings = ref<Record<string, any>>({
|
|
@@ -445,6 +604,11 @@ const currentEntryPassSettings = ref<Record<string, any>>({
|
|
|
445
604
|
disclaimerMessage: "",
|
|
446
605
|
url: "",
|
|
447
606
|
template: { id: "", name: "" },
|
|
607
|
+
onboardingEnabled: false,
|
|
608
|
+
onboardingHeader: "",
|
|
609
|
+
onboardingSubText: "",
|
|
610
|
+
onboardingPrinterVendorId: "",
|
|
611
|
+
onboardingPrinterProductId: "",
|
|
448
612
|
status: "",
|
|
449
613
|
createdBy: "",
|
|
450
614
|
updatedBy: "",
|
|
@@ -474,6 +638,18 @@ const isShowTemplatePreview = ref(false);
|
|
|
474
638
|
const uploadingTemplate = ref(false);
|
|
475
639
|
const templateFileInput = ref<HTMLInputElement | null>(null);
|
|
476
640
|
|
|
641
|
+
const scanningOnboardingPrinter = ref(false);
|
|
642
|
+
const testingOnboardingPrinter = ref(false);
|
|
643
|
+
const printingOnboardingQr = ref(false);
|
|
644
|
+
const isShowOnboardingPreview = ref(false);
|
|
645
|
+
|
|
646
|
+
/** The URL printed on the self-service QR code — the public, unauthenticated
|
|
647
|
+
page a walk-up visitor lands on after scanning it at the gate. */
|
|
648
|
+
const runtimeConfig = useRuntimeConfig().public as Record<string, any>;
|
|
649
|
+
const onboardingQrValue = computed(
|
|
650
|
+
() => `${runtimeConfig.APP_PROPERTY_MANAGEMENT}/public-view/visitor-onboarding/${siteId}`
|
|
651
|
+
);
|
|
652
|
+
|
|
477
653
|
function triggerTemplateUpload() {
|
|
478
654
|
templateFileInput.value?.click();
|
|
479
655
|
}
|
|
@@ -614,6 +790,15 @@ watchEffect(() => {
|
|
|
614
790
|
disclaimerMessage: data.settings?.disclaimerMessage ?? "",
|
|
615
791
|
url: data.settings?.url ?? "",
|
|
616
792
|
template: data.settings?.template ?? { id: "", name: "" },
|
|
793
|
+
onboardingEnabled: data.settings?.onboarding?.enabled ?? false,
|
|
794
|
+
onboardingHeader: data.settings?.onboarding?.header ?? "",
|
|
795
|
+
onboardingSubText: data.settings?.onboarding?.subText ?? "",
|
|
796
|
+
onboardingPrinterVendorId: data.settings?.onboarding?.printer?.vendorId
|
|
797
|
+
? String(data.settings.onboarding.printer.vendorId)
|
|
798
|
+
: "",
|
|
799
|
+
onboardingPrinterProductId: data.settings?.onboarding?.printer?.productId
|
|
800
|
+
? String(data.settings.onboarding.printer.productId)
|
|
801
|
+
: "",
|
|
617
802
|
createdAt: data.createdAt ?? "",
|
|
618
803
|
updatedAt: data.updatedAt ?? "",
|
|
619
804
|
};
|
|
@@ -651,6 +836,15 @@ function buildPayload() {
|
|
|
651
836
|
template: currentEntryPassSettings.value.template?.id
|
|
652
837
|
? currentEntryPassSettings.value.template
|
|
653
838
|
: null,
|
|
839
|
+
onboarding: {
|
|
840
|
+
enabled: currentEntryPassSettings.value.onboardingEnabled ?? false,
|
|
841
|
+
header: currentEntryPassSettings.value.onboardingHeader || "",
|
|
842
|
+
subText: currentEntryPassSettings.value.onboardingSubText || "",
|
|
843
|
+
printer: {
|
|
844
|
+
vendorId: Number(currentEntryPassSettings.value.onboardingPrinterVendorId) || null,
|
|
845
|
+
productId: Number(currentEntryPassSettings.value.onboardingPrinterProductId) || null,
|
|
846
|
+
},
|
|
847
|
+
},
|
|
654
848
|
},
|
|
655
849
|
};
|
|
656
850
|
}
|
|
@@ -693,6 +887,101 @@ async function saveEntryPassUrl() {
|
|
|
693
887
|
getSiteEntryPassSettings();
|
|
694
888
|
}
|
|
695
889
|
|
|
890
|
+
async function updateOnboardingEnabled() {
|
|
891
|
+
await _save(buildPayload());
|
|
892
|
+
showMessage("Self-service auto-approval updated!", "success");
|
|
893
|
+
getSiteEntryPassSettings();
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
async function scanOnboardingPrinter() {
|
|
897
|
+
if (!isWebUsbSupported.value) {
|
|
898
|
+
showMessage("Web USB is not supported in this browser", "error");
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
try {
|
|
902
|
+
scanningOnboardingPrinter.value = true;
|
|
903
|
+
const device = await requestDevice();
|
|
904
|
+
currentEntryPassSettings.value.onboardingPrinterVendorId = String(device.vendorId);
|
|
905
|
+
currentEntryPassSettings.value.onboardingPrinterProductId = String(device.productId);
|
|
906
|
+
showMessage(
|
|
907
|
+
`Printer found: ${device.productName} (${device.vendorId}:${device.productId})`,
|
|
908
|
+
"success",
|
|
909
|
+
);
|
|
910
|
+
} catch (error: any) {
|
|
911
|
+
if (error.message !== "No device selected") {
|
|
912
|
+
showMessage(error.message || "Failed to scan printer", "error");
|
|
913
|
+
}
|
|
914
|
+
} finally {
|
|
915
|
+
scanningOnboardingPrinter.value = false;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
async function testAndPrintOnboarding() {
|
|
920
|
+
if (!isWebUsbSupported.value) {
|
|
921
|
+
showMessage("Web USB is not supported in this browser", "error");
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
const vendorId = Number(currentEntryPassSettings.value.onboardingPrinterVendorId);
|
|
925
|
+
const productId = Number(currentEntryPassSettings.value.onboardingPrinterProductId);
|
|
926
|
+
if (!vendorId || !productId) {
|
|
927
|
+
showMessage("Please set a valid Vendor ID and Product ID first", "warning");
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
try {
|
|
931
|
+
testingOnboardingPrinter.value = true;
|
|
932
|
+
const result = await testConnection(vendorId, productId);
|
|
933
|
+
showMessage(result.message, result.success ? "success" : "error");
|
|
934
|
+
} catch (error: any) {
|
|
935
|
+
showMessage(error.message || "Test print failed", "error");
|
|
936
|
+
} finally {
|
|
937
|
+
testingOnboardingPrinter.value = false;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
async function saveOnboardingSettings() {
|
|
942
|
+
await _save(buildPayload());
|
|
943
|
+
showMessage("Self-service settings saved successfully!", "success");
|
|
944
|
+
getSiteEntryPassSettings();
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
async function printOnboardingQrCode() {
|
|
948
|
+
if (!isWebUsbSupported.value) {
|
|
949
|
+
showMessage("Web USB is not supported in this browser", "error");
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
const vendorId = Number(currentEntryPassSettings.value.onboardingPrinterVendorId);
|
|
953
|
+
const productId = Number(currentEntryPassSettings.value.onboardingPrinterProductId);
|
|
954
|
+
if (!vendorId || !productId) {
|
|
955
|
+
showMessage("Please set up and save a printer first", "warning");
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
try {
|
|
959
|
+
printingOnboardingQr.value = true;
|
|
960
|
+
// Header/subtext are printed onto the physical QR, so persist them
|
|
961
|
+
// before sending to the printer - otherwise a printed pass could show
|
|
962
|
+
// text the settings screen never actually saved.
|
|
963
|
+
await _save(buildPayload());
|
|
964
|
+
const result = await testConnection(
|
|
965
|
+
vendorId,
|
|
966
|
+
productId,
|
|
967
|
+
true,
|
|
968
|
+
onboardingQrValue.value,
|
|
969
|
+
null,
|
|
970
|
+
null,
|
|
971
|
+
undefined,
|
|
972
|
+
undefined,
|
|
973
|
+
currentEntryPassSettings.value.onboardingHeader,
|
|
974
|
+
currentEntryPassSettings.value.onboardingSubText,
|
|
975
|
+
"onboarding",
|
|
976
|
+
);
|
|
977
|
+
showMessage(result.message, result.success ? "success" : "error");
|
|
978
|
+
} catch (error: any) {
|
|
979
|
+
showMessage(error.message || "Failed to print QR code", "error");
|
|
980
|
+
} finally {
|
|
981
|
+
printingOnboardingQr.value = false;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
696
985
|
function showMessage(msg: string, color: string) {
|
|
697
986
|
message.value = msg;
|
|
698
987
|
messageColor.value = color;
|