@7365admin1/layer-common 4.35.1-staging.385 → 4.37.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 +16 -4
- package/assets/css/primitives.css +0 -43
- package/components/AccessCardAddForm.vue +4 -5
- package/components/AccessCardAssignToUnitForm.vue +2 -3
- package/components/AccessCardPreviewDialog.vue +3 -4
- package/components/AppSelect.vue +3 -77
- package/components/CameraForm.vue +6 -20
- package/components/DashboardMain.vue +60 -66
- package/components/Dialog/UpdateMoreAction.vue +1 -11
- package/components/DocumentForm.vue +112 -407
- package/components/DocumentManagement.vue +12 -213
- package/components/EntryPass/QrTemplatePreview.vue +25 -21
- package/components/EntryPassInformation.vue +24 -119
- package/components/Facility/BookingSetup.vue +0 -63
- package/components/HidAccessLogDashboard.vue +17 -74
- package/components/HidIntercomManagement.vue +14 -180
- package/components/HidQrCodeConfiguration.vue +195 -17
- package/components/HidReaderManagement.vue +0 -398
- package/components/HidReaderUserRoster.vue +3 -44
- package/components/HidUserEnrollment.vue +155 -845
- package/components/InvitationClientForm.vue +1 -19
- package/components/Layout/NavigationDrawer.vue +1 -4
- package/components/NotificationBell.vue +7 -59
- package/components/RolePermissionFormCreate.vue +3 -4
- package/components/RolePermissionFormPreviewUpdate.vue +5 -12
- package/components/RolePermissionMain.vue +0 -1
- package/components/ServiceProviderInvitationPrompt.vue +155 -0
- package/components/ServiceProviderMain.vue +30 -178
- package/components/VisitorForm.vue +0 -12
- package/components/VisitorManagement.vue +10 -285
- package/components/VisitorSocketPopUp.vue +1 -2
- package/composables/useANPRSettings.ts +15 -45
- package/composables/useAccessManagement.ts +0 -5
- package/composables/useCleaningPermission.ts +0 -2
- package/composables/useCommonPermission.ts +0 -32
- package/composables/useDocument.ts +0 -7
- package/composables/useFacility.ts +0 -11
- package/composables/useHidAmico.ts +0 -159
- package/composables/useHidNavigation.ts +1 -25
- package/composables/usePeople.ts +0 -19
- package/composables/usePropertyPermission.ts +0 -19
- package/composables/useSecurityPermission.ts +0 -19
- package/composables/useServiceProvider.ts +0 -3
- package/composables/useServiceProviderInvitation.ts +3 -101
- package/composables/useSiteEntryPassSettings.ts +0 -26
- package/composables/useWebUsb.ts +11 -14
- package/package.json +2 -2
- package/pages/[org]/[site]/access-mgmt/administrator/index.vue +1 -1
- package/pages/[org]/[site]/access-mgmt/hid-cards/index.vue +1 -1
- package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +1 -1
- package/plugins/API.ts +0 -11
- package/types/document.d.ts +0 -14
- package/types/facility.d.ts +0 -4
- package/types/people.d.ts +1 -28
- package/utils/module-routes.ts +1 -8
- package/utils/nav-sections.ts +2 -8
- package/utils/notification-target.ts +8 -277
- package/utils/permission-labels.ts +2 -20
- package/components/ClientInvitations.vue +0 -291
- package/components/HidAccessPermissions.vue +0 -424
- package/components/HidProfileQrCode.vue +0 -332
- package/composables/useHidReaderSelection.ts +0 -50
- package/composables/useSiteCategory.ts +0 -45
- package/pages/[org]/[site]/access-mgmt/hid-qr-code/index.vue +0 -23
- package/utils/dashboard-widget-prefs.ts +0 -54
- package/utils/hid-permission-assignments.ts +0 -190
- package/utils/hid-reader-selection.ts +0 -52
- package/utils/occupancy-role.ts +0 -109
- package/utils/sp-inviter-row.ts +0 -97
|
@@ -388,27 +388,9 @@ watch(
|
|
|
388
388
|
const roles = ref<any[]>([])
|
|
389
389
|
const { getRoles } = useRole()
|
|
390
390
|
|
|
391
|
-
/*
|
|
392
|
-
* `type` is the ROLE type, not the service the client subscribes to, and the
|
|
393
|
-
* only caller (`ClientMain.vue` "Invite Client") passes no `app` prop -- so
|
|
394
|
-
* this sent `type: ""` and the endpoint applied no type filter at all. The
|
|
395
|
-
* dropdown therefore listed every org-less role of every type, which includes
|
|
396
|
-
* the platform's OWN staff roles (`type: "admin"` -- the two rows the console's
|
|
397
|
-
* `/super-admin/role-permissions` screen edits). Offering a Seven365 console
|
|
398
|
-
* staff role on a CLIENT invitation is the defect.
|
|
399
|
-
*
|
|
400
|
-
* A client admin's role is `type: "organization"`, which is what the client
|
|
401
|
-
* roles screen (`org/[organization]/roles-permissions.vue`) already lists. This
|
|
402
|
-
* narrows to exactly that. It is a narrowing of this one picker only: no role
|
|
403
|
-
* changes, no permission changes, and the org-less `organization` roles that
|
|
404
|
-
* were already being picked are all still offered.
|
|
405
|
-
*
|
|
406
|
-
* Keying off the selected service instead would empty the dropdown -- no
|
|
407
|
-
* org-less role carries a service type -- so `props.app` is not the filter.
|
|
408
|
-
*/
|
|
409
391
|
const { data: roleData, refresh: refreshRoles } = await useLazyAsyncData(
|
|
410
392
|
"roles",
|
|
411
|
-
() => getRoles({ org: org.value?._id, type:
|
|
393
|
+
() => getRoles({ org: org.value?._id, type: props.app, limit: 50 }),
|
|
412
394
|
{ watch: [() => props.app] }
|
|
413
395
|
)
|
|
414
396
|
|
|
@@ -129,9 +129,6 @@ import { withoutDeniedRoutes } from "../../utils/module-routes";
|
|
|
129
129
|
const props = defineProps({
|
|
130
130
|
navigationItems: { type: Array, required: true },
|
|
131
131
|
title: { type: String, default: "" },
|
|
132
|
-
// An app's own name for a section heading (`withSections`), e.g.
|
|
133
|
-
// `{ "SECURITY OPERATIONS": "ESTATE SECURITY" }` in Property Management.
|
|
134
|
-
sectionLabels: { type: Object, default: () => ({}) },
|
|
135
132
|
});
|
|
136
133
|
|
|
137
134
|
const emit = defineEmits(["navigate"]);
|
|
@@ -199,7 +196,7 @@ const navigationItems = computed(() => {
|
|
|
199
196
|
return withoutDeniedRoutes(items, userAppModulesDenied.value);
|
|
200
197
|
});
|
|
201
198
|
|
|
202
|
-
const sectionedItems = computed(() => withSections(navigationItems.value
|
|
199
|
+
const sectionedItems = computed(() => withSections(navigationItems.value));
|
|
203
200
|
|
|
204
201
|
/**
|
|
205
202
|
* The design's brand tile carries the product's own mark. One icon for eleven
|
|
@@ -70,10 +70,7 @@
|
|
|
70
70
|
:key="item._id"
|
|
71
71
|
type="button"
|
|
72
72
|
class="notif-card"
|
|
73
|
-
:class="{
|
|
74
|
-
'notif-card--unread': !item.hasRead,
|
|
75
|
-
'notif-card--plain': !targetFor(item),
|
|
76
|
-
}"
|
|
73
|
+
:class="{ 'notif-card--unread': !item.hasRead }"
|
|
77
74
|
@click="openNotification(item)"
|
|
78
75
|
>
|
|
79
76
|
<div class="notif-card__top">
|
|
@@ -137,7 +134,7 @@ import {
|
|
|
137
134
|
timeAgo,
|
|
138
135
|
type TInboxItem,
|
|
139
136
|
} from "../utils/notification-inbox";
|
|
140
|
-
import {
|
|
137
|
+
import { notificationTarget } from "../utils/notification-target";
|
|
141
138
|
|
|
142
139
|
/**
|
|
143
140
|
* The top-bar notification feed for the web, in every signed-in app.
|
|
@@ -171,7 +168,6 @@ const { getAll, getUnreadCount, markAsRead, markAllAsRead } = useNotification();
|
|
|
171
168
|
const { currentUser } = useLocalAuth();
|
|
172
169
|
const route = useRoute();
|
|
173
170
|
const router = useRouter();
|
|
174
|
-
const appConfig = useRuntimeConfig();
|
|
175
171
|
|
|
176
172
|
const open = ref(false);
|
|
177
173
|
const loading = ref(false);
|
|
@@ -307,59 +303,16 @@ function markRead(item: Record<string, any>) {
|
|
|
307
303
|
* layer: the ten apps that have no order queue simply do not match it, and the
|
|
308
304
|
* click there stays what it always was.
|
|
309
305
|
*/
|
|
310
|
-
function targetFor(item: Record<string, any>) {
|
|
311
|
-
return notificationTarget(item as any, {
|
|
312
|
-
// The org app nests under `organization`; the service apps under `org`.
|
|
313
|
-
// Staff pages and the org app have neither, so the person's own default
|
|
314
|
-
// organisation builds a service-app link from there.
|
|
315
|
-
organization: String(
|
|
316
|
-
route.params?.organization ?? route.params?.org ?? currentUser.value?.defaultOrg ?? "",
|
|
317
|
-
),
|
|
318
|
-
site: String(route.params?.site ?? ""),
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/** A path this app really has a page for (every app has a 404 fallback, so check the match). */
|
|
323
|
-
function hasLocalPage(path: string) {
|
|
324
|
-
try {
|
|
325
|
-
const resolved = router.resolve(path);
|
|
326
|
-
return resolved.matched.length > 0 && !resolved.matched.some((m) => m.path.includes("pathMatch"));
|
|
327
|
-
} catch {
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/** Every web app's URL, keyed like `TAppKey`, from the layer's runtime config. */
|
|
333
|
-
function appUrls() {
|
|
334
|
-
const p = appConfig?.public ?? {};
|
|
335
|
-
return {
|
|
336
|
-
org: String(p.APP_ORG ?? ""),
|
|
337
|
-
security: String(p.APP_SECURITY ?? ""),
|
|
338
|
-
"property-management": String(p.APP_PROPERTY_MANAGEMENT ?? ""),
|
|
339
|
-
hygiene: String(p.APP_HYGIENE ?? ""),
|
|
340
|
-
"mechanical-electrical": String(p.APP_MECHANICAL_ELECTRICAL ?? ""),
|
|
341
|
-
"pest-control": String(p.APP_PEST_CONTROL ?? ""),
|
|
342
|
-
landscaping: String(p.APP_LANDSCAPING ?? ""),
|
|
343
|
-
"pool-maintenance": String(p.APP_POOL_MAINTENANCE ?? ""),
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/** The decision is `chooseNavigation` (unit-tested); this only carries it out. */
|
|
348
306
|
async function goToSubject(item: Record<string, any>) {
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
hasPage: hasLocalPage,
|
|
352
|
-
appUrls: appUrls(),
|
|
307
|
+
const target = notificationTarget(item as any, {
|
|
308
|
+
organization: String(route.params?.organization ?? ""),
|
|
353
309
|
});
|
|
354
|
-
if (!
|
|
310
|
+
if (!target) return;
|
|
311
|
+
if (typeof router.hasRoute !== "function" || !router.hasRoute(target.name)) return;
|
|
355
312
|
|
|
356
313
|
open.value = false;
|
|
357
|
-
if (nav.type === "href") {
|
|
358
|
-
window.location.assign(nav.href);
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
314
|
try {
|
|
362
|
-
await router.push(
|
|
315
|
+
await router.push(target);
|
|
363
316
|
} catch {
|
|
364
317
|
// A cancelled or duplicate navigation is not worth an error in the top bar.
|
|
365
318
|
}
|
|
@@ -498,11 +451,6 @@ onBeforeUnmount(() => {
|
|
|
498
451
|
background: var(--hover);
|
|
499
452
|
}
|
|
500
453
|
|
|
501
|
-
/* Nothing to open: still marks read on click, but does not look like a link. */
|
|
502
|
-
.notif-card--plain {
|
|
503
|
-
cursor: default;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
454
|
.notif-card__top {
|
|
507
455
|
display: flex;
|
|
508
456
|
align-items: baseline;
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
</template>
|
|
140
140
|
<v-list-item-title>
|
|
141
141
|
<span class="text-capitalize role-create__row-title">
|
|
142
|
-
{{ formatResourceLabel(String(resourceKey)
|
|
142
|
+
{{ formatResourceLabel(String(resourceKey)) }}
|
|
143
143
|
</span>
|
|
144
144
|
</v-list-item-title>
|
|
145
145
|
<template #append>
|
|
@@ -279,7 +279,6 @@ const props = defineProps({
|
|
|
279
279
|
},
|
|
280
280
|
});
|
|
281
281
|
|
|
282
|
-
|
|
283
282
|
const emit = defineEmits(["cancel", "success", "success:create-more"]);
|
|
284
283
|
|
|
285
284
|
const validForm = ref(false);
|
|
@@ -538,14 +537,14 @@ const moduleSearch = ref<string | null>("");
|
|
|
538
537
|
/** `safePermissions`, narrowed to what the search box matches. */
|
|
539
538
|
const visiblePermissions = computed(() => {
|
|
540
539
|
const query = (moduleSearch.value ?? "").trim().toLowerCase();
|
|
541
|
-
const all = sortResourcesByLabel(safePermissions.value
|
|
540
|
+
const all = sortResourcesByLabel(safePermissions.value);
|
|
542
541
|
if (!query) return all;
|
|
543
542
|
|
|
544
543
|
const matches: Record<string, any> = {};
|
|
545
544
|
for (const [resource, actions] of Object.entries(all)) {
|
|
546
545
|
const haystack = [
|
|
547
546
|
resource,
|
|
548
|
-
formatResourceLabel(resource
|
|
547
|
+
formatResourceLabel(resource),
|
|
549
548
|
...Object.keys(actions ?? {}).flatMap((action) => [
|
|
550
549
|
action,
|
|
551
550
|
formatActionLabel(action),
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
/>
|
|
103
103
|
</template>
|
|
104
104
|
<v-list-item-title class="text-capitalize role-perm__row-title">
|
|
105
|
-
{{ formatResourceLabel(String(resourceKey)
|
|
105
|
+
{{ formatResourceLabel(String(resourceKey)) }}
|
|
106
106
|
</v-list-item-title>
|
|
107
107
|
<template #append>
|
|
108
108
|
<div class="role-perm__cat-actions">
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
<v-divider v-if="resourceIndex > 0" />
|
|
192
192
|
<v-list-item density="compact" :ripple="false">
|
|
193
193
|
<v-list-item-title class="text-capitalize role-perm__row-title">
|
|
194
|
-
{{ formatResourceLabel(String(resourceKey)
|
|
194
|
+
{{ formatResourceLabel(String(resourceKey)) }}
|
|
195
195
|
</v-list-item-title>
|
|
196
196
|
<v-list-item-subtitle class="role-perm__row-sub">
|
|
197
197
|
{{
|
|
@@ -311,15 +311,8 @@ const props = defineProps({
|
|
|
311
311
|
type: Number,
|
|
312
312
|
default: 0,
|
|
313
313
|
},
|
|
314
|
-
// The role type the editor was opened for; only changes a module's NAME
|
|
315
|
-
// where one app calls a key something else (`RESOURCE_LABELS_BY_TYPE`).
|
|
316
|
-
type: {
|
|
317
|
-
type: String,
|
|
318
|
-
default: "",
|
|
319
|
-
},
|
|
320
314
|
});
|
|
321
315
|
|
|
322
|
-
|
|
323
316
|
/** A role holding the wildcard has ONE stored entry and reaches everything. */
|
|
324
317
|
const holdsWildcard = computed(() => (definedModel.value ?? []).includes("*"));
|
|
325
318
|
|
|
@@ -493,14 +486,14 @@ const moduleSearch = ref<string | null>("");
|
|
|
493
486
|
|
|
494
487
|
const visiblePermissions = computed(() => {
|
|
495
488
|
const query = (moduleSearch.value ?? "").trim().toLowerCase();
|
|
496
|
-
const all = sortResourcesByLabel(safePermissions.value
|
|
489
|
+
const all = sortResourcesByLabel(safePermissions.value);
|
|
497
490
|
if (!query) return all;
|
|
498
491
|
|
|
499
492
|
const matches: Record<string, any> = {};
|
|
500
493
|
for (const [resource, actions] of Object.entries(all)) {
|
|
501
494
|
const haystack = [
|
|
502
495
|
resource,
|
|
503
|
-
formatResourceLabel(resource
|
|
496
|
+
formatResourceLabel(resource),
|
|
504
497
|
...Object.keys((actions as Record<string, unknown>) ?? {}).flatMap(
|
|
505
498
|
(action) => [action, formatActionLabel(action)]
|
|
506
499
|
),
|
|
@@ -516,7 +509,7 @@ const visiblePermissions = computed(() => {
|
|
|
516
509
|
// Same A-Z as the tree above it; a second list in a second order reads as a
|
|
517
510
|
// different kind of thing.
|
|
518
511
|
const sortedNotOffered = computed(() =>
|
|
519
|
-
sortResourcesByLabel(props.notOfferedPermissions as Record<string, any
|
|
512
|
+
sortResourcesByLabel(props.notOfferedPermissions as Record<string, any>)
|
|
520
513
|
);
|
|
521
514
|
|
|
522
515
|
/*
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- `AppCard` is the design's card - 14px radius, a 1px `--border` rule and
|
|
3
|
+
the one-pixel shadow on `--card`. The `v-card rounded="lg"
|
|
4
|
+
variant="outlined"` it replaced was Vuetify's own card at its own
|
|
5
|
+
radius and border weight. -->
|
|
6
|
+
<AppCard v-if="invitation" class="sp-invite-prompt">
|
|
7
|
+
<div class="d-flex align-start ga-3 pa-4">
|
|
8
|
+
<v-avatar color="primary" variant="tonal" size="44">
|
|
9
|
+
<v-icon>mdi-office-building-marker-outline</v-icon>
|
|
10
|
+
</v-avatar>
|
|
11
|
+
<div class="flex-grow-1">
|
|
12
|
+
<div class="screen-card-title">
|
|
13
|
+
{{ invitation.orgName }} has invited you to {{ invitation.siteName }}
|
|
14
|
+
</div>
|
|
15
|
+
<div class="screen-hint sp-invite-prompt__sub">
|
|
16
|
+
Approved by Seven365 · {{ expiryLine }}
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="px-4 sp-invite-prompt__body">
|
|
22
|
+
{{ invitation.orgName }} would like your company to provide services at
|
|
23
|
+
<strong>{{ invitation.siteName }}</strong
|
|
24
|
+
>. If you accept, this site will be added to your list of sites.
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="d-flex align-center ga-2 px-4 py-4">
|
|
28
|
+
<!-- Both stay `v-btn`s: `loading` is the only signal that an accept or a
|
|
29
|
+
decline is in flight, and `AppButton` has no equivalent. They take
|
|
30
|
+
the design's two button classes instead. -->
|
|
31
|
+
<v-btn
|
|
32
|
+
variant="flat"
|
|
33
|
+
class="screen-btn-primary"
|
|
34
|
+
:loading="working === 'accept'"
|
|
35
|
+
:disabled="Boolean(working) || !open"
|
|
36
|
+
@click="accept"
|
|
37
|
+
>
|
|
38
|
+
Accept
|
|
39
|
+
</v-btn>
|
|
40
|
+
<v-btn
|
|
41
|
+
variant="outlined"
|
|
42
|
+
class="screen-btn-ghost"
|
|
43
|
+
:loading="working === 'decline'"
|
|
44
|
+
:disabled="Boolean(working) || !open"
|
|
45
|
+
@click="decline"
|
|
46
|
+
>
|
|
47
|
+
Decline
|
|
48
|
+
</v-btn>
|
|
49
|
+
<v-spacer />
|
|
50
|
+
<!-- Was a raw `v-chip` on Vuetify's fixed `success` green, or no colour
|
|
51
|
+
at all for every other outcome. `StatusChip` maps the status WORD
|
|
52
|
+
through the shared `utils/status.ts` tones, so "complete" and
|
|
53
|
+
"declined" come out the same colours they do everywhere else. -->
|
|
54
|
+
<StatusChip
|
|
55
|
+
v-if="!open"
|
|
56
|
+
:status="invitation.status"
|
|
57
|
+
:label="invitation.statusLabel"
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
</AppCard>
|
|
61
|
+
|
|
62
|
+
<AppCard v-else-if="error">
|
|
63
|
+
<div class="pa-4 sp-invite-prompt__body">{{ error }}</div>
|
|
64
|
+
</AppCard>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script setup lang="ts">
|
|
68
|
+
/**
|
|
69
|
+
* The invitation as the service provider sees it — inside the app they already
|
|
70
|
+
* use, from the session they already have.
|
|
71
|
+
*
|
|
72
|
+
* There is deliberately no sign-up and no one-time code anywhere on this
|
|
73
|
+
* screen: a company that already exists on iService365 never creates a second
|
|
74
|
+
* account. The email they receive only carries them here.
|
|
75
|
+
*
|
|
76
|
+
* An invitation that has not been approved by Seven365 is not readable at all —
|
|
77
|
+
* the server answers "not found" — so this component cannot show one.
|
|
78
|
+
*/
|
|
79
|
+
const props = defineProps({
|
|
80
|
+
invitationId: { type: String, required: true },
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const emit = defineEmits<{ (e: "answered", status: string): void }>();
|
|
84
|
+
|
|
85
|
+
const { getInvitation, declineInvitation } = useServiceProviderInvitation();
|
|
86
|
+
const { addViaInvite } = useCustomerSite();
|
|
87
|
+
|
|
88
|
+
const invitation = ref<any>(null);
|
|
89
|
+
const error = ref("");
|
|
90
|
+
const working = ref("");
|
|
91
|
+
|
|
92
|
+
const open = computed(() => invitation.value?.status === "pending");
|
|
93
|
+
|
|
94
|
+
const expiryLine = computed(() => {
|
|
95
|
+
const expires = invitation.value?.expireAt;
|
|
96
|
+
if (!expires) return "";
|
|
97
|
+
const days = Math.max(
|
|
98
|
+
0,
|
|
99
|
+
Math.ceil((new Date(expires).getTime() - Date.now()) / 86400000),
|
|
100
|
+
);
|
|
101
|
+
if (!open.value) return "no longer waiting for an answer";
|
|
102
|
+
return days <= 1 ? "expires within a day" : `expires in ${days} days`;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
async function load() {
|
|
106
|
+
try {
|
|
107
|
+
invitation.value = await getInvitation(props.invitationId);
|
|
108
|
+
} catch (err: any) {
|
|
109
|
+
error.value =
|
|
110
|
+
err?.data?.message || "This invitation is no longer available.";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function accept() {
|
|
115
|
+
working.value = "accept";
|
|
116
|
+
try {
|
|
117
|
+
await addViaInvite(props.invitationId);
|
|
118
|
+
await load();
|
|
119
|
+
emit("answered", "complete");
|
|
120
|
+
} catch (err: any) {
|
|
121
|
+
error.value = err?.data?.message || "Could not accept this invitation.";
|
|
122
|
+
} finally {
|
|
123
|
+
working.value = "";
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function decline() {
|
|
128
|
+
working.value = "decline";
|
|
129
|
+
try {
|
|
130
|
+
await declineInvitation(props.invitationId);
|
|
131
|
+
await load();
|
|
132
|
+
emit("answered", "declined");
|
|
133
|
+
} catch (err: any) {
|
|
134
|
+
error.value = err?.data?.message || "Could not decline this invitation.";
|
|
135
|
+
} finally {
|
|
136
|
+
working.value = "";
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await load();
|
|
141
|
+
</script>
|
|
142
|
+
|
|
143
|
+
<style scoped>
|
|
144
|
+
/* The title was a hand-set 1.05rem/600; `.screen-card-title` is the design's
|
|
145
|
+
15.5px/800, so only the wrap and the leading are left here. */
|
|
146
|
+
.sp-invite-prompt :deep(.screen-card-title) {
|
|
147
|
+
display: block;
|
|
148
|
+
line-height: 1.35;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.sp-invite-prompt__sub,
|
|
152
|
+
.sp-invite-prompt__body {
|
|
153
|
+
font-size: var(--fs-cell);
|
|
154
|
+
}
|
|
155
|
+
</style>
|