@edgedev/create-edge-app 1.1.28 → 1.2.29
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/agents.md +2 -0
- package/bin/cli.js +13 -5
- package/deploy-services.sh +237 -0
- package/deploy.sh +88 -1
- package/edge/components/auth/register.vue +51 -0
- package/edge/components/cms/block.vue +29 -16
- package/edge/components/cms/blockEditor.vue +748 -7
- package/edge/components/cms/codeEditor.vue +24 -2
- package/edge/components/cms/htmlContent.vue +10 -2
- package/edge/components/cms/mediaManager.vue +19 -3
- package/edge/components/cms/menu.vue +231 -34
- package/edge/components/cms/optionsSelect.vue +20 -3
- package/edge/components/cms/page.vue +9 -0
- package/edge/components/cms/site.vue +114 -5
- package/edge/components/cms/siteSettingsForm.vue +7 -0
- package/edge/components/cms/themeEditor.vue +9 -3
- package/edge/components/dashboard.vue +22 -3
- package/edge/components/imagePicker.vue +126 -0
- package/edge/components/myAccount.vue +1 -0
- package/edge/components/myProfile.vue +345 -61
- package/edge/components/organizationMembers.vue +569 -261
- package/edge/components/shad/combobox.vue +2 -2
- package/edge/components/shad/number.vue +2 -2
- package/edge/composables/global.ts +5 -2
- package/edge/composables/structuredDataTemplates.js +6 -6
- package/firebase_init.sh +63 -2
- package/package.json +1 -1
- package/services/.deploy.shared.env.example +12 -0
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
// TODO: pass possible roles in prop
|
|
3
3
|
import { toTypedSchema } from '@vee-validate/zod'
|
|
4
|
+
import { ArrowLeft, Loader2, Trash2, User } from 'lucide-vue-next'
|
|
4
5
|
import * as z from 'zod'
|
|
5
6
|
const props = defineProps({
|
|
6
7
|
usersCollectionPath: {
|
|
7
8
|
type: String,
|
|
8
9
|
default: () => `organizations/${edgeGlobal.edgeState.currentOrganization}`,
|
|
9
10
|
},
|
|
11
|
+
defaultImageTags: {
|
|
12
|
+
type: Array,
|
|
13
|
+
default: () => [
|
|
14
|
+
'Headshots',
|
|
15
|
+
],
|
|
16
|
+
},
|
|
10
17
|
metaFields: {
|
|
11
18
|
type: Array,
|
|
12
19
|
default: () => [
|
|
@@ -49,6 +56,11 @@ const props = defineProps({
|
|
|
49
56
|
name: z
|
|
50
57
|
.string({ required_error: 'Name is required' })
|
|
51
58
|
.min(1, { message: 'Name is required' }),
|
|
59
|
+
email: z
|
|
60
|
+
.string({ required_error: 'Email is required' })
|
|
61
|
+
.email({ message: 'Invalid email address' })
|
|
62
|
+
.min(6, { message: 'Email must be at least 6 characters long' })
|
|
63
|
+
.max(50, { message: 'Email must be less than 50 characters long' }),
|
|
52
64
|
}),
|
|
53
65
|
role: z
|
|
54
66
|
.string({ required_error: 'Role is required' })
|
|
@@ -56,6 +68,10 @@ const props = defineProps({
|
|
|
56
68
|
}),
|
|
57
69
|
),
|
|
58
70
|
},
|
|
71
|
+
metaFieldsSchema: {
|
|
72
|
+
type: Object,
|
|
73
|
+
default: null,
|
|
74
|
+
},
|
|
59
75
|
})
|
|
60
76
|
// TODO: If a removed user no longer has roles to any organiztions, need to a create new organization for them with
|
|
61
77
|
// default name of "Personal". This will allow them to continue to use the app.
|
|
@@ -65,6 +81,7 @@ const route = useRoute()
|
|
|
65
81
|
const edgeFirebase = inject('edgeFirebase')
|
|
66
82
|
const state = reactive({
|
|
67
83
|
filter: '',
|
|
84
|
+
roleFilter: 'all',
|
|
68
85
|
workingItem: {},
|
|
69
86
|
dialog: false,
|
|
70
87
|
form: false,
|
|
@@ -120,12 +137,22 @@ const showInviteOrgSelect = computed(() => inviteOrgOptions.value.length > 1)
|
|
|
120
137
|
const showEditOrgSelect = computed(() => editOrgOptions.value.length > 1)
|
|
121
138
|
const showRemoveOrgSelect = computed(() => removeOrgOptions.value.length > 1)
|
|
122
139
|
|
|
140
|
+
const adminCount = computed(() => {
|
|
141
|
+
return users.value.filter((item) => {
|
|
142
|
+
return item.roles.find((role) => {
|
|
143
|
+
return role.collectionPath === edgeGlobal.edgeState.organizationDocPath.replaceAll('/', '-') && role.role === 'admin'
|
|
144
|
+
})
|
|
145
|
+
}).length
|
|
146
|
+
})
|
|
147
|
+
|
|
123
148
|
const selfRemoveBlocked = computed(() => {
|
|
124
149
|
return state.workingItem.userId === edgeFirebase.user.uid
|
|
125
150
|
&& adminCount.value === 1
|
|
126
151
|
&& state.removeOrgIds.includes(edgeGlobal.edgeState.currentOrganization)
|
|
127
152
|
})
|
|
128
153
|
|
|
154
|
+
const emailDisabledHint = 'This field is tied to the user\'s username and can only be changed by them.'
|
|
155
|
+
|
|
129
156
|
const WIDTHS = {
|
|
130
157
|
1: 'md:col-span-1',
|
|
131
158
|
2: 'md:col-span-2',
|
|
@@ -143,6 +170,78 @@ const WIDTHS = {
|
|
|
143
170
|
|
|
144
171
|
const numColsToTailwind = cols => WIDTHS[cols] || 'md:col-span-12'
|
|
145
172
|
|
|
173
|
+
const disabledNoteText = 'Contact admin to change.'
|
|
174
|
+
|
|
175
|
+
const getDisabledNote = (field) => {
|
|
176
|
+
if (!field?.disabled)
|
|
177
|
+
return ''
|
|
178
|
+
return field?.disabledNote || disabledNoteText
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const mergeDisabledNote = (text, field) => {
|
|
182
|
+
const note = getDisabledNote(field)
|
|
183
|
+
if (!note)
|
|
184
|
+
return text || ''
|
|
185
|
+
if (text)
|
|
186
|
+
return `${text} ${note}`
|
|
187
|
+
return note
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const userKey = (user) => {
|
|
191
|
+
return user?.docId || user?.userId || user?.id || user?.uid || ''
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const userRoleName = (user) => {
|
|
195
|
+
return String(edgeGlobal.getRoleName(user?.roles, edgeGlobal.edgeState.currentOrganization) || '').trim()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const selectedRole = computed(() => {
|
|
199
|
+
if (state.roleFilter === 'all' || state.roleFilter === 'no-role')
|
|
200
|
+
return ''
|
|
201
|
+
return String(state.roleFilter || '').trim()
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
const roleFilterOptions = computed(() => {
|
|
205
|
+
const allRoleNames = Array.from(new Set([
|
|
206
|
+
...roleNamesOnly.value,
|
|
207
|
+
...users.value.map(user => userRoleName(user)),
|
|
208
|
+
]
|
|
209
|
+
.map(name => String(name || '').trim())
|
|
210
|
+
.filter(Boolean)))
|
|
211
|
+
.sort((a, b) => a.localeCompare(b))
|
|
212
|
+
|
|
213
|
+
return [
|
|
214
|
+
{ name: 'All Roles', docId: 'all' },
|
|
215
|
+
{ name: 'No Role', docId: 'no-role' },
|
|
216
|
+
...allRoleNames.map(role => ({ name: role, docId: role })),
|
|
217
|
+
]
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
const usersByRoleFilter = computed(() => {
|
|
221
|
+
if (state.roleFilter === 'all')
|
|
222
|
+
return users.value
|
|
223
|
+
if (state.roleFilter === 'no-role')
|
|
224
|
+
return users.value.filter(user => !userRoleName(user))
|
|
225
|
+
if (!selectedRole.value)
|
|
226
|
+
return users.value
|
|
227
|
+
return users.value.filter(user => userRoleName(user) === selectedRole.value)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
const PROFILE_IMAGE_SIZE = 96
|
|
231
|
+
const PROFILE_IMAGE_VARIANT = `width=${PROFILE_IMAGE_SIZE},height=${PROFILE_IMAGE_SIZE},fit=cover,quality=85`
|
|
232
|
+
|
|
233
|
+
const profileImageUrl = (url) => {
|
|
234
|
+
if (!url || typeof url !== 'string')
|
|
235
|
+
return ''
|
|
236
|
+
if (url.includes('/cdn-cgi/image/'))
|
|
237
|
+
return url
|
|
238
|
+
if (url.includes('width=') && url.includes('height='))
|
|
239
|
+
return url
|
|
240
|
+
if (url.endsWith('/thumbnail'))
|
|
241
|
+
return url.replace(/\/thumbnail$/, `/${PROFILE_IMAGE_VARIANT}`)
|
|
242
|
+
return url
|
|
243
|
+
}
|
|
244
|
+
|
|
146
245
|
// Helpers to read/write nested keys like "profile.firstName" on plain objects
|
|
147
246
|
function getByPath(obj, path, fallback = undefined) {
|
|
148
247
|
if (!obj || !path)
|
|
@@ -176,36 +275,40 @@ function setByPath(obj, path, value) {
|
|
|
176
275
|
}
|
|
177
276
|
|
|
178
277
|
const sortedFilteredUsers = computed(() => {
|
|
179
|
-
const filter = state.filter.toLowerCase()
|
|
278
|
+
const filter = String(state.filter || '').toLowerCase()
|
|
180
279
|
|
|
181
280
|
const getLastName = (fullName) => {
|
|
182
281
|
if (!fullName)
|
|
183
282
|
return ''
|
|
184
|
-
const parts = fullName.trim().split(/\s+/)
|
|
283
|
+
const parts = String(fullName).trim().split(/\s+/)
|
|
185
284
|
return parts[parts.length - 1] || ''
|
|
186
285
|
}
|
|
187
286
|
|
|
188
|
-
return
|
|
189
|
-
.filter(user =>
|
|
287
|
+
return usersByRoleFilter.value
|
|
288
|
+
.filter((user) => {
|
|
289
|
+
const name = String(user?.meta?.name || '').toLowerCase()
|
|
290
|
+
const email = String(user?.meta?.email || '').toLowerCase()
|
|
291
|
+
return name.includes(filter) || email.includes(filter)
|
|
292
|
+
})
|
|
190
293
|
.sort((a, b) => {
|
|
191
|
-
const lastA = getLastName(a
|
|
192
|
-
const lastB = getLastName(b
|
|
294
|
+
const lastA = getLastName(a?.meta?.name).toLowerCase()
|
|
295
|
+
const lastB = getLastName(b?.meta?.name).toLowerCase()
|
|
193
296
|
return lastA.localeCompare(lastB)
|
|
194
297
|
})
|
|
195
298
|
})
|
|
196
299
|
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
return
|
|
200
|
-
|
|
201
|
-
})
|
|
202
|
-
}).length
|
|
300
|
+
const detailViewKey = computed(() => {
|
|
301
|
+
if (!state.dialog)
|
|
302
|
+
return 'member-empty'
|
|
303
|
+
return `member-${userKey(state.workingItem) || state.workingItem?.id || 'new'}-${state.saveButton}`
|
|
203
304
|
})
|
|
204
305
|
|
|
205
306
|
const addItem = () => {
|
|
206
307
|
state.saveButton = 'Invite User'
|
|
207
308
|
const newItem = edgeGlobal.dupObject(state.newItem)
|
|
208
309
|
newItem.meta.email = ''
|
|
310
|
+
newItem.meta.name = ''
|
|
311
|
+
newItem.meta.profilephoto = ''
|
|
209
312
|
state.workingItem = newItem
|
|
210
313
|
state.workingItem.id = edgeGlobal.generateShortId()
|
|
211
314
|
state.currentTitle = 'Invite User'
|
|
@@ -367,10 +470,48 @@ const onSubmit = async () => {
|
|
|
367
470
|
state.dialog = false
|
|
368
471
|
}
|
|
369
472
|
|
|
473
|
+
const roleSchema = z
|
|
474
|
+
.string({ required_error: 'Role is required' })
|
|
475
|
+
.min(1, { message: 'Role is required' })
|
|
476
|
+
|
|
477
|
+
const baseMetaSchema = z.object({
|
|
478
|
+
name: z
|
|
479
|
+
.string({ required_error: 'Name is required' })
|
|
480
|
+
.min(1, { message: 'Name is required' }),
|
|
481
|
+
email: z
|
|
482
|
+
.string({ required_error: 'Email is required' })
|
|
483
|
+
.email({ message: 'Invalid email address' })
|
|
484
|
+
.min(6, { message: 'Email must be at least 6 characters long' })
|
|
485
|
+
.max(50, { message: 'Email must be less than 50 characters long' }),
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
const buildMetaSchema = () => {
|
|
489
|
+
const extra = props.metaFieldsSchema
|
|
490
|
+
if (!extra)
|
|
491
|
+
return baseMetaSchema
|
|
492
|
+
if (extra?.shape && typeof extra.shape === 'object')
|
|
493
|
+
return baseMetaSchema.merge(extra)
|
|
494
|
+
if (typeof extra === 'object')
|
|
495
|
+
return baseMetaSchema.extend(extra)
|
|
496
|
+
return baseMetaSchema
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const computedNewUserSchema = computed(() => {
|
|
500
|
+
if (!props.metaFieldsSchema)
|
|
501
|
+
return props.newUserSchema
|
|
502
|
+
return toTypedSchema(z.object({ meta: buildMetaSchema(), role: roleSchema }))
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
const computedUpdateUserSchema = computed(() => {
|
|
506
|
+
if (!props.metaFieldsSchema)
|
|
507
|
+
return props.updateUserSchema
|
|
508
|
+
return toTypedSchema(z.object({ meta: buildMetaSchema(), role: roleSchema }))
|
|
509
|
+
})
|
|
510
|
+
|
|
370
511
|
const computedUserSchema = computed(() =>
|
|
371
512
|
state.saveButton === 'Invite User'
|
|
372
|
-
?
|
|
373
|
-
:
|
|
513
|
+
? computedNewUserSchema.value
|
|
514
|
+
: computedUpdateUserSchema.value,
|
|
374
515
|
)
|
|
375
516
|
|
|
376
517
|
const currentOrganization = computed(() => {
|
|
@@ -406,267 +547,434 @@ onBeforeMount(async () => {
|
|
|
406
547
|
</script>
|
|
407
548
|
|
|
408
549
|
<template>
|
|
409
|
-
<
|
|
410
|
-
<
|
|
411
|
-
<
|
|
412
|
-
<
|
|
413
|
-
<
|
|
414
|
-
<
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
<template #center>
|
|
419
|
-
<slot name="header-center">
|
|
420
|
-
<div class="w-full px-6" />
|
|
421
|
-
</slot>
|
|
422
|
-
</template>
|
|
423
|
-
<template #end>
|
|
424
|
-
<slot name="header-end" :add-item="addItem">
|
|
425
|
-
<edge-shad-button class="bg-primary mx-2 h-6 text-xs" @click="addItem()">
|
|
426
|
-
Invite Member
|
|
427
|
-
</edge-shad-button>
|
|
428
|
-
</slot>
|
|
429
|
-
</template>
|
|
430
|
-
</edge-menu>
|
|
431
|
-
</slot>
|
|
432
|
-
<CardContent class="p-3 w-full overflow-y-auto scroll-area">
|
|
433
|
-
<Input
|
|
434
|
-
v-model="state.filter"
|
|
435
|
-
class="mb-2"
|
|
436
|
-
placeholder="Filter members..."
|
|
437
|
-
/>
|
|
438
|
-
<div v-if="sortedFilteredUsers.length > 0">
|
|
439
|
-
<div v-for="user in sortedFilteredUsers" :key="user.id" class="flex w-full py-2 justify-between items-center cursor-pointer" @click="editItem(user)">
|
|
440
|
-
<slot name="user" :user="user">
|
|
441
|
-
<Avatar class="handle pointer p-0 h-6 w-6 mr-2">
|
|
442
|
-
<User width="18" height="18" />
|
|
443
|
-
</Avatar>
|
|
444
|
-
<div class="flex gap-2 mr-2 items-center">
|
|
445
|
-
<div class="text-md text-bold mr-2">
|
|
446
|
-
{{ user.meta.name }}
|
|
550
|
+
<div v-if="state.loaded" class="w-full flex-1 min-h-0 h-[calc(100vh-58px)] overflow-hidden">
|
|
551
|
+
<ResizablePanelGroup direction="horizontal" class="w-full h-full flex-1">
|
|
552
|
+
<ResizablePanel class="bg-sidebar text-sidebar-foreground min-w-[400px]" :default-size="22" :min-size="30">
|
|
553
|
+
<div class="flex flex-col h-full">
|
|
554
|
+
<div class="px-3 py-3 border-b border-sidebar-border bg-sidebar/90">
|
|
555
|
+
<div class="flex items-center justify-between gap-2">
|
|
556
|
+
<div class="flex items-center gap-2 text-sm font-semibold">
|
|
557
|
+
<component :is="edgeGlobal.iconFromMenu(route)" class="h-4 w-4" />
|
|
558
|
+
<span>Members</span>
|
|
447
559
|
</div>
|
|
448
|
-
<edge-
|
|
449
|
-
|
|
450
|
-
</edge-
|
|
451
|
-
<!-- <edge-chip v-if="!user.userId" class="bg-primary">
|
|
452
|
-
Invited, Not Registered
|
|
453
|
-
</edge-chip> -->
|
|
454
|
-
</div>
|
|
455
|
-
<div class="grow flex gap-2 justify-end">
|
|
456
|
-
<template v-if="!user.userId">
|
|
457
|
-
<edge-chip class="bg-slate-600 w-[200px]">
|
|
458
|
-
{{ user.docId }}
|
|
459
|
-
<edge-clipboard-button class="relative ml-1 top-[2px] mt-0" :text="user.docId" />
|
|
460
|
-
</edge-chip>
|
|
461
|
-
</template>
|
|
462
|
-
<edge-chip>
|
|
463
|
-
{{ edgeGlobal.getRoleName(user.roles, edgeGlobal.edgeState.currentOrganization) }}
|
|
464
|
-
</edge-chip>
|
|
560
|
+
<edge-shad-button size="sm" class="h-7 text-xs bg-primary" @click="addItem()">
|
|
561
|
+
Invite
|
|
562
|
+
</edge-shad-button>
|
|
465
563
|
</div>
|
|
466
|
-
<
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
</
|
|
487
|
-
<span v-else>
|
|
488
|
-
Remove "{{ state.workingItem.meta.name }}"
|
|
489
|
-
</span>
|
|
490
|
-
</DialogTitle>
|
|
491
|
-
<DialogDescription />
|
|
492
|
-
</DialogHeader>
|
|
493
|
-
|
|
494
|
-
<h3 v-if="selfRemoveBlocked">
|
|
495
|
-
You cannot remove yourself from this organization because you are the only admin. You can delete the organization or add another admin.
|
|
496
|
-
</h3>
|
|
497
|
-
<h3 v-else-if="state.workingItem.userId === edgeFirebase.user.uid">
|
|
498
|
-
<span v-if="showRemoveOrgSelect">Select the organizations you want to leave.</span>
|
|
499
|
-
<span v-else>Are you sure you want to remove yourself from the organization "{{ currentOrganization.name }}"? You will no longer have access to any of the organization's data.</span>
|
|
500
|
-
</h3>
|
|
501
|
-
<h3 v-else>
|
|
502
|
-
<span v-if="showRemoveOrgSelect">Select the organizations you want to remove "{{ state.workingItem.meta.name }}" from.</span>
|
|
503
|
-
<span v-else>Are you sure you want to remove "{{ state.workingItem.meta.name }}" from the organization "{{ currentOrganization.name }}"?</span>
|
|
504
|
-
</h3>
|
|
505
|
-
<div v-if="showRemoveOrgSelect" class="mt-4 w-full flex flex-wrap gap-2">
|
|
506
|
-
<div v-for="org in removeOrgOptions" :key="org.docId" class="flex-1 min-w-[220px]">
|
|
507
|
-
<edge-shad-checkbox
|
|
508
|
-
:name="`remove-org-${org.docId}`"
|
|
509
|
-
:model-value="state.removeOrgIds.includes(org.docId)"
|
|
510
|
-
@update:model-value="val => updateRemoveOrgSelection(org.docId, val)"
|
|
511
|
-
>
|
|
512
|
-
{{ org.name }}
|
|
513
|
-
</edge-shad-checkbox>
|
|
564
|
+
<div class="mt-3 flex items-center gap-2">
|
|
565
|
+
<div class="w-1/2 min-w-0">
|
|
566
|
+
<edge-shad-combobox
|
|
567
|
+
v-model="state.roleFilter"
|
|
568
|
+
:items="roleFilterOptions"
|
|
569
|
+
name="roleFilter"
|
|
570
|
+
item-title="name"
|
|
571
|
+
item-value="docId"
|
|
572
|
+
placeholder="Select role"
|
|
573
|
+
class="w-full !h-8"
|
|
574
|
+
/>
|
|
575
|
+
</div>
|
|
576
|
+
<div class="w-1/2 min-w-0">
|
|
577
|
+
<edge-shad-input
|
|
578
|
+
v-model="state.filter"
|
|
579
|
+
label=""
|
|
580
|
+
name="filter"
|
|
581
|
+
class="h-8 w-full"
|
|
582
|
+
placeholder="Filter members..."
|
|
583
|
+
/>
|
|
584
|
+
</div>
|
|
514
585
|
</div>
|
|
515
586
|
</div>
|
|
516
|
-
<
|
|
517
|
-
<
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
<edge-shad-checkbox
|
|
565
|
-
:name="`edit-add-org-${org.docId}`"
|
|
566
|
-
:model-value="state.editOrgIds.includes(org.docId)"
|
|
567
|
-
@update:model-value="val => updateEditOrgSelection(org.docId, val)"
|
|
587
|
+
<div class="flex-1 overflow-y-auto">
|
|
588
|
+
<SidebarMenu class="px-2 py-2 space-y-0">
|
|
589
|
+
<SidebarMenuItem
|
|
590
|
+
v-for="user in sortedFilteredUsers"
|
|
591
|
+
:key="userKey(user)"
|
|
592
|
+
>
|
|
593
|
+
<SidebarMenuButton
|
|
594
|
+
class="w-full !h-auto items-start px-3 py-2"
|
|
595
|
+
:class="state.dialog && userKey(state.workingItem) && userKey(state.workingItem) === userKey(user) ? 'bg-sidebar-accent text-sidebar-accent-foreground' : ''"
|
|
596
|
+
@click="editItem(user)"
|
|
597
|
+
>
|
|
598
|
+
<div class="flex w-full items-start gap-3" :class="!user.userId ? 'opacity-70' : ''">
|
|
599
|
+
<Avatar class="h-12 w-12 rounded-md border bg-muted/40 flex items-center justify-center overflow-hidden">
|
|
600
|
+
<img
|
|
601
|
+
v-if="user?.meta?.profilephoto"
|
|
602
|
+
:src="profileImageUrl(user.meta.profilephoto)"
|
|
603
|
+
alt=""
|
|
604
|
+
class="h-full w-full object-cover"
|
|
605
|
+
>
|
|
606
|
+
<User v-else width="24" height="24" />
|
|
607
|
+
</Avatar>
|
|
608
|
+
<div class="min-w-0 flex-1">
|
|
609
|
+
<div class="flex items-center gap-2">
|
|
610
|
+
<span class="text-sm font-medium leading-snug whitespace-normal uppercase">
|
|
611
|
+
{{ user?.meta?.name || user?.meta?.email || 'Unnamed Member' }}
|
|
612
|
+
</span>
|
|
613
|
+
<!-- <span v-if="!user.userId" class="text-[10px] uppercase tracking-wide text-muted-foreground">
|
|
614
|
+
-
|
|
615
|
+
</span> -->
|
|
616
|
+
<edge-chip v-if="user.userId === edgeFirebase.user.uid">
|
|
617
|
+
You
|
|
618
|
+
</edge-chip>
|
|
619
|
+
</div>
|
|
620
|
+
<div class="mt-1 flex flex-wrap items-center gap-2 text-[11px] text-muted-foreground leading-snug">
|
|
621
|
+
<span class="rounded-full bg-secondary px-2 py-0.5 text-[10px] text-secondary-foreground">
|
|
622
|
+
{{ edgeGlobal.getRoleName(user.roles, edgeGlobal.edgeState.currentOrganization) }}
|
|
623
|
+
</span>
|
|
624
|
+
<span v-if="!user.userId && user.docId" class="inline-flex items-center gap-1 whitespace-normal">
|
|
625
|
+
{{ user.docId }}
|
|
626
|
+
<edge-clipboard-button class="relative top-[1px]" :text="user.docId" />
|
|
627
|
+
</span>
|
|
628
|
+
</div>
|
|
629
|
+
</div>
|
|
630
|
+
<edge-shad-button
|
|
631
|
+
size="icon"
|
|
632
|
+
variant="ghost"
|
|
633
|
+
class="h-7 w-7 text-destructive/80 hover:text-destructive"
|
|
634
|
+
@click.stop="deleteConfirm(user)"
|
|
568
635
|
>
|
|
569
|
-
|
|
570
|
-
</edge-shad-
|
|
636
|
+
<Trash2 class="h-4 w-4" />
|
|
637
|
+
</edge-shad-button>
|
|
571
638
|
</div>
|
|
572
|
-
</
|
|
639
|
+
</SidebarMenuButton>
|
|
640
|
+
<Separator class="my-1" />
|
|
641
|
+
</SidebarMenuItem>
|
|
642
|
+
<div v-if="sortedFilteredUsers.length === 0" class="px-4 py-6 text-xs text-muted-foreground">
|
|
643
|
+
No members found.
|
|
573
644
|
</div>
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
645
|
+
</SidebarMenu>
|
|
646
|
+
</div>
|
|
647
|
+
</div>
|
|
648
|
+
</ResizablePanel>
|
|
649
|
+
<ResizablePanel class="bg-background">
|
|
650
|
+
<div class="h-full flex flex-col">
|
|
651
|
+
<Transition name="fade" mode="out-in">
|
|
652
|
+
<div v-if="state.dialog" :key="detailViewKey" class="h-full flex flex-col">
|
|
653
|
+
<edge-shad-form
|
|
654
|
+
:key="userKey(state.workingItem) || state.workingItem?.id || 'member-form'"
|
|
655
|
+
:initial-values="state.workingItem"
|
|
656
|
+
:schema="computedUserSchema"
|
|
657
|
+
class="flex flex-col h-full"
|
|
658
|
+
@submit="onSubmit"
|
|
659
|
+
>
|
|
660
|
+
<div class="flex items-center justify-between border-b bg-secondary px-4 py-3">
|
|
661
|
+
<div class="text-sm font-semibold">
|
|
662
|
+
{{ state.currentTitle || 'Member' }}
|
|
663
|
+
</div>
|
|
664
|
+
<div class="flex items-center gap-2">
|
|
665
|
+
<edge-shad-button variant="text" class="text-xs text-red-700" @click="closeDialog">
|
|
666
|
+
Close
|
|
667
|
+
</edge-shad-button>
|
|
668
|
+
<edge-shad-button
|
|
669
|
+
type="submit"
|
|
670
|
+
class="text-xs bg-primary"
|
|
671
|
+
:disabled="state.loading"
|
|
584
672
|
>
|
|
585
|
-
|
|
586
|
-
|
|
673
|
+
<Loader2 v-if="state.loading" class="w-4 h-4 mr-2 animate-spin" />
|
|
674
|
+
{{ state.saveButton }}
|
|
675
|
+
</edge-shad-button>
|
|
587
676
|
</div>
|
|
588
677
|
</div>
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
678
|
+
<div class="flex-1 overflow-y-auto p-6 space-y-4">
|
|
679
|
+
<slot name="edit-fields" :working-item="state.workingItem">
|
|
680
|
+
<div class="rounded-xl border bg-card p-4 space-y-4 shadow-sm">
|
|
681
|
+
<div class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
682
|
+
Member Details
|
|
683
|
+
</div>
|
|
684
|
+
<div class="flex flex-col gap-4 md:flex-row md:items-stretch">
|
|
685
|
+
<div class="w-full md:w-[260px] self-stretch">
|
|
686
|
+
<edge-image-picker
|
|
687
|
+
v-model="state.workingItem.meta.profilephoto"
|
|
688
|
+
label="Profile Photo"
|
|
689
|
+
dialog-title="Select Profile Photo"
|
|
690
|
+
site="clearwater-hub-images"
|
|
691
|
+
:default-tags="props.defaultImageTags"
|
|
692
|
+
height-class="h-full min-h-[180px]"
|
|
693
|
+
:include-cms-all="false"
|
|
694
|
+
/>
|
|
695
|
+
</div>
|
|
696
|
+
<div class="flex-1 space-y-4">
|
|
697
|
+
<edge-g-input
|
|
698
|
+
v-model="state.workingItem.role"
|
|
699
|
+
name="role"
|
|
700
|
+
:disable-tracking="true"
|
|
701
|
+
:items="roleNamesOnly"
|
|
702
|
+
field-type="select"
|
|
703
|
+
label="Role"
|
|
704
|
+
:parent-tracker-id="`inviteUser-${state.workingItem.id}`"
|
|
705
|
+
:disabled="state.workingItem.userId === edgeFirebase.user.uid"
|
|
706
|
+
/>
|
|
707
|
+
<edge-g-input
|
|
708
|
+
v-model="state.workingItem.meta.name"
|
|
709
|
+
name="meta.name"
|
|
710
|
+
:disable-tracking="true"
|
|
711
|
+
field-type="text"
|
|
712
|
+
label="Name"
|
|
713
|
+
:parent-tracker-id="`inviteUser-${state.workingItem.id}`"
|
|
714
|
+
/>
|
|
715
|
+
<edge-g-input
|
|
716
|
+
v-model="state.workingItem.meta.email"
|
|
717
|
+
name="meta.email"
|
|
718
|
+
:disable-tracking="true"
|
|
719
|
+
field-type="text"
|
|
720
|
+
label="Email"
|
|
721
|
+
:disabled="state.saveButton !== 'Invite User'"
|
|
722
|
+
:hint="state.saveButton !== 'Invite User' ? emailDisabledHint : ''"
|
|
723
|
+
:persistent-hint="state.saveButton !== 'Invite User'"
|
|
724
|
+
:parent-tracker-id="`inviteUser-${state.workingItem.id}`"
|
|
725
|
+
/>
|
|
726
|
+
<edge-g-input
|
|
727
|
+
v-model="state.workingItem.meta.phone"
|
|
728
|
+
name="meta.phone"
|
|
729
|
+
:disable-tracking="true"
|
|
730
|
+
field-type="text"
|
|
731
|
+
label="Phone"
|
|
732
|
+
:mask-options="{ mask: '(###) ###-####' }"
|
|
733
|
+
:parent-tracker-id="`inviteUser-${state.workingItem.id}`"
|
|
734
|
+
/>
|
|
735
|
+
</div>
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
<div v-if="state.saveButton !== 'Invite User' && showEditOrgSelect" class="mt-4 w-full">
|
|
739
|
+
<div class="text-sm font-medium text-foreground">
|
|
740
|
+
Organizations
|
|
741
|
+
</div>
|
|
742
|
+
<div class="mt-2 w-full flex flex-wrap gap-2">
|
|
743
|
+
<div v-for="org in editOrgOptions" :key="org.docId" class="flex-1 min-w-[220px]">
|
|
744
|
+
<edge-shad-checkbox
|
|
745
|
+
:name="`edit-add-org-${org.docId}`"
|
|
746
|
+
:model-value="state.editOrgIds.includes(org.docId)"
|
|
747
|
+
@update:model-value="val => updateEditOrgSelection(org.docId, val)"
|
|
748
|
+
>
|
|
749
|
+
{{ org.name }}
|
|
750
|
+
</edge-shad-checkbox>
|
|
751
|
+
</div>
|
|
752
|
+
</div>
|
|
753
|
+
</div>
|
|
754
|
+
<div v-if="state.saveButton === 'Invite User' && showInviteOrgSelect" class="mt-4 w-full">
|
|
755
|
+
<div class="text-sm font-medium text-foreground">
|
|
756
|
+
Add to organizations
|
|
757
|
+
</div>
|
|
758
|
+
<div class="mt-2 w-full flex flex-wrap gap-2">
|
|
759
|
+
<div v-for="org in inviteOrgOptions" :key="org.docId" class="flex-1 min-w-[220px]">
|
|
760
|
+
<edge-shad-checkbox
|
|
761
|
+
:name="`invite-org-${org.docId}`"
|
|
762
|
+
:model-value="state.inviteOrgIds.includes(org.docId)"
|
|
763
|
+
@update:model-value="val => updateInviteOrgSelection(org.docId, val)"
|
|
764
|
+
>
|
|
765
|
+
{{ org.name }}
|
|
766
|
+
</edge-shad-checkbox>
|
|
767
|
+
</div>
|
|
768
|
+
</div>
|
|
769
|
+
</div>
|
|
770
|
+
<Separator class="my-6" />
|
|
771
|
+
<div class="grid grid-cols-12 gap-2">
|
|
772
|
+
<div v-for="field in props.metaFields" :key="field.field" class="mb-3 col-span-12" :class="numColsToTailwind(field.cols)">
|
|
773
|
+
<!-- Use explicit model binding so dotted paths (e.g., "address.street") work -->
|
|
774
|
+
<edge-image-picker
|
|
775
|
+
v-if="field?.type === 'imagePicker'"
|
|
776
|
+
:model-value="getByPath(state.workingItem.meta, field.field, '')"
|
|
777
|
+
:label="field?.label || 'Photo'"
|
|
778
|
+
:dialog-title="field?.dialogTitle || 'Select Image'"
|
|
779
|
+
:site="field?.site || 'clearwater-hub-images'"
|
|
780
|
+
:default-tags="field?.tags || []"
|
|
781
|
+
:height-class="field?.heightClass || 'h-[160px]'"
|
|
782
|
+
:disabled="field?.disabled || false"
|
|
783
|
+
:include-cms-all="false"
|
|
784
|
+
@update:model-value="val => setByPath(state.workingItem.meta, field.field, val)"
|
|
785
|
+
/>
|
|
786
|
+
<p v-if="field?.type === 'imagePicker' && field?.disabled" class="mt-1 text-xs text-muted-foreground">
|
|
787
|
+
{{ getDisabledNote(field) }}
|
|
788
|
+
</p>
|
|
789
|
+
<div v-else-if="field?.type === 'richText'" class="member-richtext">
|
|
790
|
+
<edge-shad-html
|
|
791
|
+
:model-value="getByPath(state.workingItem.meta, field.field, '')"
|
|
792
|
+
:name="`meta.${field.field}`"
|
|
793
|
+
:label="field?.label"
|
|
794
|
+
:disabled="field?.disabled || false"
|
|
795
|
+
:description="mergeDisabledNote(field?.description, field)"
|
|
796
|
+
:enabled-toggles="field?.enabledToggles || ['bold', 'italic', 'strike', 'bulletlist', 'orderedlist', 'underline']"
|
|
797
|
+
@update:model-value="val => setByPath(state.workingItem.meta, field.field, val)"
|
|
798
|
+
/>
|
|
799
|
+
</div>
|
|
800
|
+
<edge-shad-select-tags
|
|
801
|
+
v-else-if="field?.type === 'selectTags'"
|
|
802
|
+
:model-value="getByPath(state.workingItem.meta, field.field, [])"
|
|
803
|
+
:name="`meta.${field.field}`"
|
|
804
|
+
:label="field?.label"
|
|
805
|
+
:description="mergeDisabledNote(field?.description, field)"
|
|
806
|
+
:items="field?.items || []"
|
|
807
|
+
:item-title="field?.itemTitle || 'title'"
|
|
808
|
+
:item-value="field?.itemValue || 'name'"
|
|
809
|
+
:allow-additions="field?.allowAdditions || false"
|
|
810
|
+
:placeholder="field?.placeholder"
|
|
811
|
+
:disabled="field?.disabled || false"
|
|
812
|
+
@update:model-value="val => setByPath(state.workingItem.meta, field.field, val)"
|
|
813
|
+
/>
|
|
814
|
+
<div v-else-if="field?.type === 'boolean'" class="space-y-1 -mt-3">
|
|
815
|
+
<div class="text-sm font-medium leading-none opacity-0 select-none h-4">
|
|
816
|
+
{{ field?.label || '' }}
|
|
817
|
+
</div>
|
|
818
|
+
<edge-g-input
|
|
819
|
+
:model-value="getByPath(state.workingItem.meta, field.field, false)"
|
|
820
|
+
:name="`meta.${field.field}`"
|
|
821
|
+
:field-type="field?.type"
|
|
822
|
+
:label="field?.label"
|
|
823
|
+
parent-tracker-id="user-settings"
|
|
824
|
+
:disable-tracking="true"
|
|
825
|
+
:disabled="field?.disabled || false"
|
|
826
|
+
@update:model-value="val => setByPath(state.workingItem.meta, field.field, val)"
|
|
827
|
+
/>
|
|
828
|
+
<p v-if="mergeDisabledNote(field?.hint, field)" class="text-xs text-muted-foreground">
|
|
829
|
+
{{ mergeDisabledNote(field?.hint, field) }}
|
|
830
|
+
</p>
|
|
831
|
+
</div>
|
|
832
|
+
<edge-g-input
|
|
833
|
+
v-else-if="field?.type === 'textarea'"
|
|
834
|
+
:model-value="getByPath(state.workingItem.meta, field.field, '')"
|
|
835
|
+
:name="`meta.${field.field}`"
|
|
836
|
+
:field-type="field?.type"
|
|
837
|
+
:label="field?.label"
|
|
838
|
+
parent-tracker-id="user-settings"
|
|
839
|
+
:hint="mergeDisabledNote(field?.hint, field)"
|
|
840
|
+
:persistent-hint="Boolean(mergeDisabledNote(field?.hint, field))"
|
|
841
|
+
:disable-tracking="true"
|
|
842
|
+
:bindings="{ class: 'h-60' }"
|
|
843
|
+
:mask-options="field?.maskOptions"
|
|
844
|
+
:disabled="field?.disabled || false"
|
|
845
|
+
@update:model-value="val => setByPath(state.workingItem.meta, field.field, val)"
|
|
846
|
+
/>
|
|
847
|
+
<edge-shad-tags
|
|
848
|
+
v-else-if="field?.type === 'tags' || field?.type === 'commaTags'"
|
|
849
|
+
:model-value="getByPath(state.workingItem.meta, field.field, '')"
|
|
850
|
+
:name="`meta.${field.field}`"
|
|
851
|
+
:field-type="field?.type"
|
|
852
|
+
:label="field?.label"
|
|
853
|
+
parent-tracker-id="user-settings"
|
|
854
|
+
:description="mergeDisabledNote(field?.description || field?.hint, field)"
|
|
855
|
+
:disable-tracking="true"
|
|
856
|
+
:disabled="field?.disabled || false"
|
|
857
|
+
@update:model-value="val => setByPath(state.workingItem.meta, field.field, val)"
|
|
858
|
+
/>
|
|
859
|
+
<edge-g-input
|
|
860
|
+
v-else
|
|
861
|
+
:model-value="getByPath(state.workingItem.meta, field.field, '')"
|
|
862
|
+
:name="`meta.${field.field}`"
|
|
863
|
+
:field-type="field?.type"
|
|
864
|
+
:label="field?.label"
|
|
865
|
+
parent-tracker-id="user-settings"
|
|
866
|
+
:hint="mergeDisabledNote(field?.hint, field)"
|
|
867
|
+
:persistent-hint="Boolean(mergeDisabledNote(field?.hint, field))"
|
|
868
|
+
:disable-tracking="true"
|
|
869
|
+
:mask-options="field?.maskOptions"
|
|
870
|
+
:disabled="field?.disabled || false"
|
|
871
|
+
@update:model-value="val => setByPath(state.workingItem.meta, field.field, val)"
|
|
872
|
+
/>
|
|
873
|
+
</div>
|
|
874
|
+
</div>
|
|
875
|
+
|
|
876
|
+
<edge-g-input
|
|
877
|
+
v-if="state.saveButton === 'Invite User'"
|
|
878
|
+
v-model="state.workingItem.isTemplate"
|
|
879
|
+
name="isTemplate"
|
|
880
|
+
:disable-tracking="true"
|
|
881
|
+
field-type="boolean"
|
|
882
|
+
label="Template User"
|
|
883
|
+
:parent-tracker-id="`inviteUser-${state.workingItem.id}`"
|
|
884
|
+
/>
|
|
885
|
+
</slot>
|
|
637
886
|
</div>
|
|
887
|
+
</edge-shad-form>
|
|
888
|
+
</div>
|
|
889
|
+
<div v-else :key="detailViewKey" class="p-4 text-center flex text-slate-500 h-[calc(100vh-4rem)] justify-center items-center overflow-y-auto">
|
|
890
|
+
<div class="text-4xl">
|
|
891
|
+
<ArrowLeft class="inline-block w-12 h-12 mr-2" /> Select a member to view details.
|
|
638
892
|
</div>
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
893
|
+
</div>
|
|
894
|
+
</Transition>
|
|
895
|
+
</div>
|
|
896
|
+
</ResizablePanel>
|
|
897
|
+
</ResizablePanelGroup>
|
|
898
|
+
|
|
899
|
+
<edge-shad-dialog
|
|
900
|
+
v-model="state.deleteDialog"
|
|
901
|
+
>
|
|
902
|
+
<DialogContent>
|
|
903
|
+
<DialogHeader>
|
|
904
|
+
<DialogTitle>
|
|
905
|
+
<span v-if="state.workingItem.userId === edgeFirebase.user.uid">
|
|
906
|
+
Remove Yourself?
|
|
907
|
+
</span>
|
|
908
|
+
<span v-else>
|
|
909
|
+
Remove "{{ state.workingItem.meta.name }}"
|
|
910
|
+
</span>
|
|
911
|
+
</DialogTitle>
|
|
912
|
+
<DialogDescription />
|
|
913
|
+
</DialogHeader>
|
|
914
|
+
|
|
915
|
+
<h3 v-if="selfRemoveBlocked">
|
|
916
|
+
You cannot remove yourself from this organization because you are the only admin. You can delete the organization or add another admin.
|
|
917
|
+
</h3>
|
|
918
|
+
<h3 v-else-if="state.workingItem.userId === edgeFirebase.user.uid">
|
|
919
|
+
<span v-if="showRemoveOrgSelect">Select the organizations you want to leave.</span>
|
|
920
|
+
<span v-else>Are you sure you want to remove yourself from the organization "{{ currentOrganization.name }}"? You will no longer have access to any of the organization's data.</span>
|
|
921
|
+
</h3>
|
|
922
|
+
<h3 v-else>
|
|
923
|
+
<span v-if="showRemoveOrgSelect">Select the organizations you want to remove "{{ state.workingItem.meta.name }}" from.</span>
|
|
924
|
+
<span v-else>Are you sure you want to remove "{{ state.workingItem.meta.name }}" from the organization "{{ currentOrganization.name }}"?</span>
|
|
925
|
+
</h3>
|
|
926
|
+
<div v-if="showRemoveOrgSelect" class="mt-4 w-full flex flex-wrap gap-2">
|
|
927
|
+
<div v-for="org in removeOrgOptions" :key="org.docId" class="flex-1 min-w-[220px]">
|
|
928
|
+
<edge-shad-checkbox
|
|
929
|
+
:name="`remove-org-${org.docId}`"
|
|
930
|
+
:model-value="state.removeOrgIds.includes(org.docId)"
|
|
931
|
+
@update:model-value="val => updateRemoveOrgSelection(org.docId, val)"
|
|
932
|
+
>
|
|
933
|
+
{{ org.name }}
|
|
934
|
+
</edge-shad-checkbox>
|
|
935
|
+
</div>
|
|
936
|
+
</div>
|
|
937
|
+
<DialogFooter class="pt-6 flex justify-between">
|
|
938
|
+
<edge-shad-button class="text-white bg-slate-800 hover:bg-slate-400" @click="state.deleteDialog = false">
|
|
939
|
+
Cancel
|
|
940
|
+
</edge-shad-button>
|
|
941
|
+
<edge-shad-button
|
|
942
|
+
:disabled="selfRemoveBlocked"
|
|
943
|
+
class="w-full"
|
|
944
|
+
variant="destructive"
|
|
945
|
+
@click="deleteAction()"
|
|
946
|
+
>
|
|
947
|
+
<span v-if="state.workingItem.userId === edgeFirebase.user.uid">
|
|
948
|
+
Leave
|
|
949
|
+
</span>
|
|
950
|
+
<span v-else>
|
|
951
|
+
Remove
|
|
952
|
+
</span>
|
|
953
|
+
</edge-shad-button>
|
|
954
|
+
</DialogFooter>
|
|
955
|
+
</DialogContent>
|
|
956
|
+
</edge-shad-dialog>
|
|
957
|
+
</div>
|
|
668
958
|
</template>
|
|
669
959
|
|
|
670
960
|
<style lang="scss" scoped>
|
|
961
|
+
:deep(.member-richtext .tiptap) {
|
|
962
|
+
min-height: 220px;
|
|
963
|
+
padding: 0.75rem 1rem;
|
|
964
|
+
}
|
|
671
965
|
|
|
966
|
+
:deep(.member-richtext .tiptap p) {
|
|
967
|
+
margin-top: 0;
|
|
968
|
+
margin-bottom: 1rem;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
.fade-enter-active,
|
|
972
|
+
.fade-leave-active {
|
|
973
|
+
transition: opacity 0.3s ease;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.fade-enter-from,
|
|
977
|
+
.fade-leave-to {
|
|
978
|
+
opacity: 0;
|
|
979
|
+
}
|
|
672
980
|
</style>
|