@live-change/access-control-frontend 0.0.3

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.
@@ -0,0 +1,119 @@
1
+ <template>
2
+ <div v-if="synchronizedAccesses.length > 0" class="mb-4">
3
+ <div class="text-900 font-medium text-xl mb-2">Authorized</div>
4
+ <div v-for="access of synchronizedAccesses" :key="access.to"
5
+ class="flex flex-row flex-wrap align-items-center">
6
+ <div class="col-12 md:col-6 py-1">
7
+ <UserIdentification :ownerType="access.sessionOrUserType" :owner="access.sessionOrUser"
8
+ :data="access.identification" />
9
+ </div>
10
+ <div class="col-12 md:col-6 flex flex-row pr-0" v-if="isMounted">
11
+ <Dropdown v-if="!multiRole && (access.roles?.length ?? 0) <= 1"
12
+ id="userPublicAccess" class="w-14em"
13
+ style="width: calc(100% - 2.357rem) !important"
14
+ :options="['none'].concat(availableRoles)"
15
+ :optionLabel="optionLabel"
16
+ :modelValue="access.roles?.[0] ?? 'none'"
17
+ @update:modelValue="newValue => access.roles = [newValue]"
18
+ :feedback="false" toggleMask />
19
+ <MultiSelect v-else id="userPublicAccess"
20
+ style="width: calc(100% - 2.357rem) !important"
21
+ :options="availableRoles"
22
+ :optionLabel="optionLabel"
23
+ v-model="access.roles"
24
+ :feedback="false" toggleMask />
25
+ <Button @click="deleteAccess(access)" icon="pi pi-times"
26
+ class="p-button-rounded p-button-text p-button-plain ml-2 px-3"
27
+ style="padding-top: 0.77rem" />
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </template>
32
+
33
+ <script setup>
34
+
35
+ import Button from "primevue/button"
36
+ import Dropdown from "primevue/dropdown"
37
+ import MultiSelect from "primevue/multiselect"
38
+
39
+ import { useToast } from 'primevue/usetoast'
40
+ const toast = useToast()
41
+ import { useConfirm } from 'primevue/useconfirm'
42
+ const confirm = useConfirm()
43
+
44
+ import { UserIdentification } from "@live-change/user-frontend"
45
+ import { synchronized, synchronizedList } from "@live-change/vue3-components"
46
+
47
+ import { computed, watch, ref, onMounted } from 'vue'
48
+
49
+ const { object, objectType, availableRoles, multiRole } = defineProps({
50
+ object: {
51
+ type: String,
52
+ required: true
53
+ },
54
+ objectType: {
55
+ type: String,
56
+ required: true
57
+ },
58
+ availableRoles: {
59
+ type: Array,
60
+ default: () => ['reader']
61
+ },
62
+ multiRole: {
63
+ type: Boolean,
64
+ default: false
65
+ }
66
+ })
67
+
68
+ const isMounted = ref(false)
69
+ onMounted(() => isMounted.value = true)
70
+
71
+ function optionLabel(option) {
72
+ if(option == 'none') return 'none'
73
+ return option
74
+ }
75
+
76
+ import { path, live, actions } from '@live-change/vue3-ssr'
77
+ const accessControlApi = actions().accessControl
78
+
79
+ const [ accesses ] = await Promise.all([
80
+ live(path().accessControl.objectOwnedAccesses({ object, objectType })
81
+ .with(access => path().userIdentification.sessionOrUserOwnedIdentification({
82
+ sessionOrUserType: access.sessionOrUserType, sessionOrUser: access.sessionOrUser
83
+ }).bind('identification'))
84
+ )
85
+ ])
86
+
87
+ const synchronizedAccessesList = synchronizedList({
88
+ source: accesses,
89
+ update: accessControlApi.updateSessionOrUserAndObjectOwnedAccess,
90
+ delete: accessControlApi.resetSessionOrUserAndObjectOwnedAccess,
91
+ identifiers: { object, objectType },
92
+ objectIdentifiers: ({ to, sessionOrUser, sessionOrUserType }) =>
93
+ ({ access: to, sessionOrUser, sessionOrUserType, object, objectType }),
94
+ onSave: () => toast.add({ severity: 'info', summary: 'Access saved', life: 1500 }),
95
+ recursive: true
96
+ })
97
+ const synchronizedAccesses = synchronizedAccessesList.value
98
+
99
+ function deleteAccess(access) {
100
+ console.log("DELETE ACCESS", access)
101
+ confirm.require({
102
+ target: event.currentTarget,
103
+ message: `Do you want to revoke user "${access.identification.name}" access?`,
104
+ icon: 'pi pi-info-circle',
105
+ acceptClass: 'p-button-danger',
106
+ accept: async () => {
107
+ await synchronizedAccessesList.delete(access)
108
+ /*await accessControlApi.deleteObjectRelatedAccess({
109
+ access: access.to, object: access.object, objectType: access.objectType
110
+ })*/
111
+ toast.add({ severity:'info', summary: 'Access Revoked', life: 1500 })
112
+ },
113
+ reject: () => {
114
+ toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
115
+ }
116
+ })
117
+ }
118
+
119
+ </script>
@@ -0,0 +1,132 @@
1
+ <template>
2
+ <div v-if="synchronizedAccessRequests.length > 0" class="mb-4">
3
+ <div class="text-900 font-medium text-xl mb-2">Access Requests</div>
4
+ <div v-for="access of synchronizedAccessRequests" :key="access.to"
5
+ class="flex flex-row flex-wrap align-items-center">
6
+ <div class="col-12 md:col-6 py-1">
7
+ <UserIdentification :ownerType="access.sessionOrUserType" :owner="access.sessionOrUser"
8
+ :data="access.identification" />
9
+ </div>
10
+ <div class="col-12 md:col-6 flex flex-row pr-0" v-if="isMounted">
11
+ <Dropdown v-if="!multiRole && (access.roles?.length ?? 0) <= 1" id="userPublicAccess" class="w-14em"
12
+ style="width: calc(100% - 4.714rem) !important"
13
+ :options="['none'].concat(availableRoles)"
14
+ :optionLabel="optionLabel"
15
+ :modelValue="access.roles?.[0] ?? 'none'"
16
+ @update:modelValue="newValue => access.roles = [newValue]"
17
+ :feedback="false" toggleMask />
18
+ <MultiSelect v-else id="userPublicAccess"
19
+ style="width: calc(100% - 4.714rem) !important"
20
+ :options="availableRoles"
21
+ :optionLabel="optionLabel"
22
+ v-model="access.roles"
23
+ :feedback="false" toggleMask />
24
+ <Button @click="acceptAccessRequest(access)" icon="pi pi-check"
25
+ class="p-button-rounded p-button-text p-button-plain ml-2 px-3"
26
+ style="padding-top: 0.77rem" />
27
+ <Button @click="deleteAccessRequest(access)" icon="pi pi-times"
28
+ class="p-button-rounded p-button-text p-button-plain px-3"
29
+ style="padding-top: 0.77rem" />
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </template>
34
+
35
+ <script setup>
36
+
37
+ import Button from "primevue/button"
38
+ import Dropdown from "primevue/dropdown"
39
+ import MultiSelect from "primevue/multiselect"
40
+
41
+ import { useToast } from 'primevue/usetoast'
42
+ const toast = useToast()
43
+ import { useConfirm } from 'primevue/useconfirm'
44
+ const confirm = useConfirm()
45
+
46
+ import { UserIdentification } from "@live-change/user-frontend"
47
+ import { synchronized, synchronizedList } from "@live-change/vue3-components"
48
+
49
+ import { computed, watch, ref, onMounted } from 'vue'
50
+
51
+ const { object, objectType, availableRoles, multiRole } = defineProps({
52
+ object: {
53
+ type: String,
54
+ required: true
55
+ },
56
+ objectType: {
57
+ type: String,
58
+ required: true
59
+ },
60
+ availableRoles: {
61
+ type: Array,
62
+ default: () => ['reader']
63
+ },
64
+ multiRole: {
65
+ type: Boolean,
66
+ default: false
67
+ }
68
+ })
69
+
70
+ const isMounted = ref(false)
71
+ onMounted(() => isMounted.value = true)
72
+
73
+ function optionLabel(option) {
74
+ if(option == 'none') return 'none'
75
+ return option
76
+ }
77
+
78
+ import { path, live, actions } from '@live-change/vue3-ssr'
79
+ const accessControlApi = actions().accessControl
80
+
81
+ const [ accessRequests ] = await Promise.all([
82
+ live(path().accessControl.objectOwnedAccessRequests({ object, objectType })
83
+ .with(access => path().userIdentification.sessionOrUserOwnedIdentification({
84
+ sessionOrUserType: access.sessionOrUserType, sessionOrUser: access.sessionOrUser
85
+ }).bind('identification'))
86
+ .action('delete', ({ sessionOrUserType, sessionOrUser, objectType, object }) =>
87
+ path().accessControl.resetSessionOrUserAndObjectOwnedAccessRequest(
88
+ { sessionOrUserType, sessionOrUser, objectType, object }
89
+ )
90
+ )
91
+ )
92
+ ])
93
+
94
+ const synchronizedAccessRequestsList = synchronizedList({
95
+ source: accessRequests,
96
+ update: accessControlApi.updateSessionOrUserAndObjectOwnedAccessRequest,
97
+ delete: accessControlApi.resetSessionOrUserAndObjectOwnedAccessRequest,
98
+ identifiers: { object, objectType },
99
+ objectIdentifiers: ({ to, sessionOrUser, sessionOrUserType }) =>
100
+ ({ accessRequest: to, sessionOrUser, sessionOrUserType, object, objectType }),
101
+ onSave: () => toast.add({ severity: 'info', summary: 'Access request saved', life: 1500 }),
102
+ recursive: true
103
+ })
104
+ const synchronizedAccessRequests = synchronizedAccessRequestsList.value
105
+
106
+ function deleteAccessRequest(accessRequest) {
107
+ console.log("DELETE ACCESS REQUEST", accessRequest)
108
+ confirm.require({
109
+ target: event.currentTarget,
110
+ message: `Do you want to delete user "${accessRequest.identification.name}" access request?`,
111
+ icon: 'pi pi-info-circle',
112
+ acceptClass: 'p-button-danger',
113
+ accept: async () => {
114
+ await synchronizedAccessRequestsList.delete(accessRequest)
115
+ //accessRequest.delete()
116
+ toast.add({ severity:'info', summary: 'Access Request Deleted', life: 1500 })
117
+ },
118
+ reject: () => {
119
+ toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
120
+ }
121
+ })
122
+ }
123
+
124
+ async function acceptAccessRequest(accessRequest) {
125
+ console.log("ACCEPT ACCESS REQUEST", accessRequest)
126
+ await accessControlApi.acceptAccessRequest({
127
+ ...accessRequest, access: accessRequest.to
128
+ })
129
+ toast.add({ severity:'info', summary: 'Access Request accepted', life: 1500 })
130
+ }
131
+
132
+ </script>
@@ -0,0 +1,123 @@
1
+ <template>
2
+ <div class="grid formgrid p-fluid mb-2">
3
+ <div class="p-field field mb-4 col-12 md:col-6" v-if="isMounted && sessionRolesVisible">
4
+ <label for="publicAccess" class="block text-900 font-medium mb-2">Public access:</label>
5
+ <Dropdown v-if="!multiRole && (synchronizedPublicAccess.sessionRoles?.length ?? 0) <= 1"
6
+ id="publicAccess" class="w-full" inputClass="w-full"
7
+ :options="['none'].concat(availableSessionRoles)"
8
+ :optionLabel="optionLabel"
9
+ :modelValue="synchronizedPublicAccess.sessionRoles?.[0] ?? 'none'"
10
+ @update:modelValue="newValue => synchronizedPublicAccess.sessionRoles = [newValue]"
11
+ :feedback="false" toggleMask />
12
+ <MultiSelect v-else
13
+ id="publicAccess" class="w-full" inputClass="w-full"
14
+ :options="availableSessionRoles"
15
+ :optionLabel="optionLabel"
16
+ v-model="synchronizedPublicAccess.sessionRoles"
17
+ :feedback="false" toggleMask />
18
+ </div>
19
+ <div class="p-field field mb-4 col-12 md:col-6" v-if="isMounted && userRolesVisible">
20
+ <label for="userPublicAccess" class="block text-900 font-medium mb-2">Public access for users:</label>
21
+ <Dropdown v-if="!multiRole && (synchronizedPublicAccess.userRoles?.length ?? 0) <= 1"
22
+ id="userPublicAccess" class="w-full" inputClass="w-full"
23
+ :options="['none'].concat(availableUserRoles)"
24
+ :optionLabel="optionLabel"
25
+ :modelValue="synchronizedPublicAccess.userRoles?.[0] ?? 'none'"
26
+ @update:modelValue="newValue => synchronizedPublicAccess.userRoles = [newValue]"
27
+ :feedback="false" toggleMask />
28
+ <MultiSelect v-else id="userPublicAccess" class="w-full" inputClass="w-full"
29
+ :options="availableUserRoles"
30
+ :optionLabel="optionLabel"
31
+ v-model="synchronizedPublicAccess.userRoles"
32
+ :feedback="false" toggleMask />
33
+ </div>
34
+ <div class="p-field field mb-4 col-12" v-if="isMounted && requestedRolesVisible">
35
+ <label for="availablePublicAccess" class="block text-900 font-medium mb-2">Roles available to request:</label>
36
+ <MultiSelect id="userPublicAccess" class="w-full" inputClass="w-full"
37
+ :options="availableRequestedRoles"
38
+ :optionLabel="optionLabel"
39
+ v-model="synchronizedPublicAccess.availableRoles"
40
+ :feedback="false" toggleMask />
41
+ </div>
42
+ </div>
43
+ </template>
44
+
45
+ <script setup>
46
+ import Dropdown from "primevue/dropdown"
47
+ import MultiSelect from "primevue/multiselect"
48
+
49
+ import { useToast } from 'primevue/usetoast'
50
+ const toast = useToast()
51
+
52
+ import { synchronized } from "@live-change/vue3-components"
53
+
54
+ function optionLabel(option) {
55
+ if(option == 'none') return 'none'
56
+ return option
57
+ }
58
+
59
+ import { computed, watch, ref, onMounted } from 'vue'
60
+
61
+ const {
62
+ object, objectType,
63
+ availableRequestedRoles, availableSessionRoles, availableUserRoles,
64
+ sessionRolesVisible, userRolesVisible, requestedRolesVisible,
65
+ multiRole
66
+ } = defineProps({
67
+ object: {
68
+ type: String,
69
+ required: true
70
+ },
71
+ objectType: {
72
+ type: String,
73
+ required: true
74
+ },
75
+ sessionRolesVisible: {
76
+ type: Boolean,
77
+ default: true
78
+ },
79
+ userRolesVisible: {
80
+ type: Boolean,
81
+ default: true
82
+ },
83
+ requestedRolesVisible: {
84
+ type: Boolean,
85
+ default: true
86
+ },
87
+ availableRequestedRoles: {
88
+ type: Array,
89
+ default: () => ['reader']
90
+ },
91
+ availableSessionRoles: {
92
+ type: Array,
93
+ default: () => ['reader']
94
+ },
95
+ availableUserRoles: {
96
+ type: Array,
97
+ default: () => ['reader']
98
+ },
99
+ multiRole: {
100
+ type: Boolean,
101
+ default: false
102
+ }
103
+ })
104
+
105
+ const isMounted = ref(false)
106
+ onMounted(() => isMounted.value = true)
107
+
108
+ import { path, live, actions } from '@live-change/vue3-ssr'
109
+ const accessControlApi = actions().accessControl
110
+
111
+ const [ publicAccess ] = await Promise.all([
112
+ live(path().accessControl.objectOwnedPublicAccess({ object, objectType }))
113
+ ])
114
+
115
+ const synchronizedPublicAccess = synchronized({
116
+ source: publicAccess,
117
+ update: accessControlApi.setOrUpdateObjectOwnedPublicAccess,
118
+ identifiers: { object, objectType },
119
+ recursive: true,
120
+ onSave: () => toast.add({ severity: 'info', summary: 'Public access saved', life: 1500 })
121
+ }).value
122
+
123
+ </script>
@@ -0,0 +1,10 @@
1
+
2
+ export function routes(config = {}) {
3
+ const { prefix = '/', route = (r) => r } = config
4
+
5
+ return [
6
+
7
+ ]
8
+ }
9
+
10
+ export default routes
@@ -0,0 +1,6 @@
1
+ import { clientEntry } from '@live-change/frontend-base/client-entry.js'
2
+ import App from './App.vue'
3
+ import { createRouter } from './router'
4
+
5
+
6
+ clientEntry(App, createRouter)
@@ -0,0 +1,6 @@
1
+ import { serverEntry } from '@live-change/frontend-base/server-entry.js'
2
+ import App from './App.vue'
3
+ import { createRouter } from './router'
4
+
5
+ const render = serverEntry(App, createRouter)
6
+ export { render }
@@ -0,0 +1,117 @@
1
+ <template>
2
+ <Dialog :visible="visible" @update:visible="v => $emit('update:visible', v)"
3
+ :modal="true" class="w-full sm:w-9 md:w-8 lg:w-6">
4
+ <template #header>
5
+ <h3>Invite user with email</h3>
6
+ </template>
7
+
8
+ <command-form service="accessControl" action="inviteEmail"
9
+ ref="inviteForm"
10
+ v-slot="{ data }"
11
+ :parameters="{ objectType, object }"
12
+ :initialValues="{ roles: availableRoles }"
13
+ @done="handleInvited" keepOnDone>
14
+
15
+ <div class="flex flex-row flex-wrap align-items-center" style="margin-left: -0.5rem; margin-right: -0.5rem;">
16
+ <div class="col-12 md:col-6 py-1">
17
+ <div class="p-field mb-3">
18
+ <label for="email" class="block text-900 font-medium mb-2">
19
+ Email address
20
+ </label>
21
+ <InputText id="email" type="text" class="w-full"
22
+ aria-describedby="email-help" :class="{ 'p-invalid': data.emailError }"
23
+ v-model="data.email" />
24
+ <small id="email-help" class="p-error">{{ data.emailError }}</small>
25
+ </div>
26
+ </div>
27
+ <div class="col-12 md:col-6">
28
+ <div class="p-field mb-3">
29
+ <label for="inviteAccess" class="block text-900 font-medium mb-2">
30
+ Roles
31
+ </label>
32
+ <Dropdown v-if="!multiRole" id="inviteAccess" class="w-14em w-full"
33
+ :options="['none'].concat(availableRoles)"
34
+ :optionLabel="optionLabel"
35
+ :modelValue="data.roles?.[0] ?? 'none'"
36
+ @update:modelValue="newValue => data.roles = [newValue]"
37
+ :feedback="false" toggleMask />
38
+ <MultiSelect v-if="multiRole" id="inviteAccess" class="w-full"
39
+ :options="availableRoles"
40
+ :optionLabel="optionLabel"
41
+ v-model="data.roles"
42
+ :feedback="false" toggleMask />
43
+ <small id="email-help" class="p-error">{{ data.rolesError }}</small>
44
+ </div>
45
+
46
+ </div>
47
+ </div>
48
+ <div class="p-field mb-1">
49
+ <label for="inviteMessage" class="block text-900 font-medium mb-2">
50
+ Message ( optional )
51
+ </label>
52
+ <Textarea id="inviteMessage" v-model="data.message" :autoResize="true" rows="3" class="w-full" />
53
+ </div>
54
+
55
+ </command-form>
56
+
57
+ <template #footer>
58
+ <Button label="Invite" icon="pi pi-envelope" autofocus @click="inviteForm.submit()" />
59
+ </template>
60
+ </Dialog>
61
+ </template>
62
+
63
+ <script setup>
64
+ import Button from "primevue/button"
65
+ import Dropdown from "primevue/dropdown"
66
+ import MultiSelect from "primevue/multiselect"
67
+
68
+ import Dialog from 'primevue/dialog'
69
+ import InputText from 'primevue/inputtext'
70
+ import Textarea from 'primevue/textarea'
71
+
72
+ import { useToast } from 'primevue/usetoast'
73
+ const toast = useToast()
74
+
75
+ import { ref } from 'vue'
76
+ import { toRefs } from "@vueuse/core"
77
+
78
+ const props = defineProps({
79
+ object: {
80
+ type: String,
81
+ required: true
82
+ },
83
+ objectType: {
84
+ type: String,
85
+ required: true
86
+ },
87
+ visible: {
88
+ type: Boolean,
89
+ required: true
90
+ },
91
+ availableRoles: {
92
+ type: Array,
93
+ default: () => ['reader']
94
+ },
95
+ multiRole: {
96
+ type: Boolean,
97
+ default: false
98
+ }
99
+ })
100
+
101
+ const emit = defineEmits(['update:visible'])
102
+
103
+ const { visible, availableRoles, multiRole, object, objectType } = toRefs(props)
104
+
105
+ function optionLabel(option) {
106
+ if(option == 'none') return 'none'
107
+ return option
108
+ }
109
+
110
+ const inviteForm = ref()
111
+ function handleInvited() {
112
+ emit('update:visible', false)
113
+ toast.add({ severity:'info', summary: 'Invitation sent!', life: 1500 })
114
+ console.log("INVITED", arguments)
115
+ }
116
+
117
+ </script>
@@ -0,0 +1,112 @@
1
+ <template>
2
+ <pre data-headers>{{ JSON.stringify(metadata, null, ' ') }}</pre>
3
+ <div data-html class="message m-6">
4
+ <p class="text-lg">
5
+ Hello!
6
+ </p>
7
+ <p>
8
+ <span v-if="from?.name">
9
+ Our user
10
+ <strong>{{ from.name }}</strong>
11
+ </span>
12
+ <span v-else>One of our users</span>
13
+ invited you to use X by entering your email address.
14
+ </p>
15
+ <div v-if="data.message.trim().length > 0">
16
+ <p>He left message for you:</p>
17
+ <blockquote class="font-italic">{{ data.message }}</blockquote>
18
+ </div>
19
+ <p>
20
+ if you already have an account, you can add this email to your account
21
+ and the invitation will be linked to your account.
22
+ </p>
23
+ <p>
24
+ Click the button below:
25
+ </p>
26
+ <div>
27
+ <a :href="linkAddress" class="no-underline">
28
+ <Button label="Confirm email" class="p-button-lg" />
29
+ </a>
30
+ </div>
31
+ <p>
32
+ Or copy this address to your browser address bar:<br>
33
+ <a :href="linkAddress">
34
+ {{ linkAddress }}
35
+ </a>
36
+ </p>
37
+ <p>
38
+ Let us know in case it's not for you.
39
+ </p>
40
+ <p>
41
+ See you soon<br>
42
+ Live Change Team
43
+ </p>
44
+ <img src="/images/logo128.png">
45
+ </div>
46
+ <pre class="message" data-text>
47
+ Hello!
48
+
49
+ {{ from?.name ? `Our user ${from?.name}` : 'One of our users' }} invited you to use X by entering your email address.
50
+
51
+ Click link below or copy address to your browser address bar:
52
+
53
+ {{ linkAddress }}
54
+
55
+ Let us know in case it's not for you.
56
+
57
+ See you soon
58
+ Live Change Team
59
+ </pre>
60
+ </template>
61
+
62
+ <script setup>
63
+ import Button from "primevue/button"
64
+
65
+ import { path, live, actions } from '@live-change/vue3-ssr'
66
+
67
+ const { action, contact, json } = defineProps({
68
+ contact: {
69
+ type: String,
70
+ required: true
71
+ },
72
+ json: {
73
+ type: String,
74
+ required: true
75
+ }
76
+ })
77
+
78
+ const data = JSON.parse(json)
79
+
80
+ const secrets = data.secrets
81
+ const secretLink = secrets.find(secret => secret.type == 'link')
82
+
83
+ const [ from ] = await Promise.all([
84
+ live(path().userIdentification.sessionOrUserOwnedIdentification(
85
+ { sessionOrUserType: data.fromType, sessionOrUser: data.from }))
86
+ ])
87
+
88
+ const metadata = {
89
+ from: '<noreply@flipchart.live>',
90
+ subject: `${from?.name ?? 'Our user'} invited you to ${data.objectType}`,
91
+ to: contact
92
+ }
93
+
94
+ const linkAddress = ENV_BASE_HREF + '/link/' + secretLink.secret.secretCode
95
+
96
+ </script>
97
+
98
+ <style scoped>
99
+ img {
100
+ width: 100%;
101
+ max-width: 100px;
102
+ }
103
+ .message {
104
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
105
+ color: #495057;
106
+ font-weight: 400;
107
+ }
108
+ pre {
109
+ border-top: 1px solid black;
110
+ border-bottom: 1px solid black;
111
+ }
112
+ </style>
@@ -0,0 +1,11 @@
1
+
2
+ export function routes(config = {}) {
3
+ const { prefix = '/', route = (r) => r } = config
4
+
5
+ return [
6
+ route({ name: 'accessControl:email:invite', path: '/_email/inviteWithMessage/:contact/:json',
7
+ props: true, meta: { raw: true }, component: () => import("./InviteEmail.vue") }),
8
+ ]
9
+ }
10
+
11
+ export default routes