@edgedev/create-edge-app 1.1.25 → 1.1.26
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/README.md +55 -20
- package/{agent.md → agents.md} +2 -0
- package/bin/cli.js +6 -6
- package/edge/components/auth/login.vue +384 -0
- package/edge/components/auth/register.vue +396 -0
- package/edge/components/auth.vue +108 -0
- package/edge/components/autoFileUpload.vue +215 -0
- package/edge/components/billing.vue +8 -0
- package/edge/components/buttonDivider.vue +14 -0
- package/edge/components/chip.vue +34 -0
- package/edge/components/clipboardButton.vue +42 -0
- package/edge/components/cms/block.vue +529 -0
- package/edge/components/cms/blockApi.vue +212 -0
- package/edge/components/cms/blockEditor.vue +725 -0
- package/edge/components/cms/blockInput.vue +66 -0
- package/edge/components/cms/blockPicker.vue +486 -0
- package/edge/components/cms/blockRender.vue +78 -0
- package/edge/components/cms/blockSheetContent.vue +28 -0
- package/edge/components/cms/codeEditor.vue +466 -0
- package/edge/components/cms/fontUpload.vue +327 -0
- package/edge/components/cms/htmlContent.vue +807 -0
- package/edge/components/cms/init_blocks/api_with_subarrays.html +17 -0
- package/edge/components/cms/init_blocks/array_with_collection.html +7 -0
- package/edge/components/cms/init_blocks/array_with_objects.html +7 -0
- package/edge/components/cms/init_blocks/carousel.html +103 -0
- package/edge/components/cms/init_blocks/contact_us.html +69 -0
- package/edge/components/cms/init_blocks/content_with_left_image.html +27 -0
- package/edge/components/cms/init_blocks/footer.html +24 -0
- package/edge/components/cms/init_blocks/header_divider.html +7 -0
- package/edge/components/cms/init_blocks/hero.html +35 -0
- package/edge/components/cms/init_blocks/hero_carousel.html +52 -0
- package/edge/components/cms/init_blocks/newsletter.html +117 -0
- package/edge/components/cms/init_blocks/post_content.html +7 -0
- package/edge/components/cms/init_blocks/post_title_header.html +21 -0
- package/edge/components/cms/init_blocks/posts_list.html +20 -0
- package/edge/components/cms/init_blocks/properties_showcase.html +100 -0
- package/edge/components/cms/init_blocks/property_carousel.html +59 -0
- package/edge/components/cms/init_blocks/property_detail.html +112 -0
- package/edge/components/cms/init_blocks/property_detail_header.html +34 -0
- package/edge/components/cms/init_blocks/property_results.html +137 -0
- package/edge/components/cms/init_blocks/property_search.html +75 -0
- package/edge/components/cms/init_blocks/simple_array.html +7 -0
- package/edge/components/cms/mediaCard.vue +116 -0
- package/edge/components/cms/mediaManager.vue +386 -0
- package/edge/components/cms/menu.vue +1103 -0
- package/edge/components/cms/optionsSelect.vue +107 -0
- package/edge/components/cms/page.vue +1785 -0
- package/edge/components/cms/posts.vue +1083 -0
- package/edge/components/cms/site.vue +1298 -0
- package/edge/components/cms/themeDefaultMenu.vue +548 -0
- package/edge/components/cms/themeEditor.vue +426 -0
- package/edge/components/dashboard.vue +776 -0
- package/edge/components/editor.vue +671 -0
- package/edge/components/fileTree.vue +72 -0
- package/edge/components/files.vue +89 -0
- package/edge/components/formSubtypes/myOrgs.vue +214 -0
- package/edge/components/formSubtypes/users.vue +336 -0
- package/edge/components/functionChips.vue +57 -0
- package/edge/components/gError.vue +98 -0
- package/edge/components/gHelper.vue +67 -0
- package/edge/components/gInput.vue +1331 -0
- package/edge/components/loggingIn.vue +41 -0
- package/edge/components/menu.vue +137 -0
- package/edge/components/menuContent.vue +132 -0
- package/edge/components/myAccount.vue +317 -0
- package/edge/components/myOrganizations.vue +75 -0
- package/edge/components/myProfile.vue +122 -0
- package/edge/components/orgSwitcher.vue +25 -0
- package/edge/components/organizationMembers.vue +522 -0
- package/edge/components/organizationSettings.vue +271 -0
- package/edge/components/shad/breadcrumbs.vue +35 -0
- package/edge/components/shad/button.vue +43 -0
- package/edge/components/shad/checkbox.vue +73 -0
- package/edge/components/shad/combobox.vue +238 -0
- package/edge/components/shad/datepicker.vue +184 -0
- package/edge/components/shad/dialog.vue +32 -0
- package/edge/components/shad/dropdownMenu.vue +54 -0
- package/edge/components/shad/dropdownMenuItem.vue +21 -0
- package/edge/components/shad/form.vue +59 -0
- package/edge/components/shad/html.vue +877 -0
- package/edge/components/shad/input.vue +139 -0
- package/edge/components/shad/number.vue +109 -0
- package/edge/components/shad/select.vue +151 -0
- package/edge/components/shad/selectTags.vue +278 -0
- package/edge/components/shad/switch.vue +67 -0
- package/edge/components/shad/tags.vue +137 -0
- package/edge/components/shad/textarea.vue +102 -0
- package/edge/components/shad/typeMoney.vue +167 -0
- package/edge/components/sideBar.vue +288 -0
- package/edge/components/sideBarContent.vue +268 -0
- package/edge/components/sidebarProvider.vue +33 -0
- package/edge/components/tooltip.vue +16 -0
- package/edge/components/userMenu.vue +148 -0
- package/edge/components/v/alert.vue +59 -0
- package/edge/components/v/alertTitle.vue +18 -0
- package/edge/components/v/card.vue +53 -0
- package/edge/components/v/cardActions.vue +18 -0
- package/edge/components/v/cardText.vue +18 -0
- package/edge/components/v/cardTitle.vue +20 -0
- package/edge/components/v/col.vue +56 -0
- package/edge/components/v/list.vue +46 -0
- package/edge/components/v/listItem.vue +26 -0
- package/edge/components/v/listItemTitle.vue +18 -0
- package/edge/components/v/row.vue +42 -0
- package/edge/components/v/toolbar.vue +24 -0
- package/edge/composables/global.ts +519 -0
- package/edge-pull.sh +2 -0
- package/edge-push.sh +1 -0
- package/edge-status.sh +14 -0
- package/package.json +1 -1
- package/edge-components-install.sh +0 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, defineProps, inject, nextTick, onBeforeMount, reactive, watch } from 'vue'
|
|
3
|
+
import { useToast } from '@/components/ui/toast/use-toast'
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
metaFields: {
|
|
6
|
+
type: Array,
|
|
7
|
+
required: true,
|
|
8
|
+
},
|
|
9
|
+
formSchema: {
|
|
10
|
+
type: Object,
|
|
11
|
+
required: true,
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const { toast } = useToast()
|
|
16
|
+
|
|
17
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
18
|
+
// const edgeGlobal = inject('edgeGlobal')
|
|
19
|
+
|
|
20
|
+
const state = reactive({
|
|
21
|
+
meta: {},
|
|
22
|
+
name: '',
|
|
23
|
+
form: false,
|
|
24
|
+
loaded: true,
|
|
25
|
+
loading: false,
|
|
26
|
+
})
|
|
27
|
+
const onSubmit = async () => {
|
|
28
|
+
state.loading = true
|
|
29
|
+
await edgeFirebase.setUserMeta(state.meta)
|
|
30
|
+
edgeGlobal.edgeState.changeTracker = {}
|
|
31
|
+
state.loaded = false
|
|
32
|
+
toast({
|
|
33
|
+
title: 'Updated Successfully',
|
|
34
|
+
description: 'Your profile has been updated',
|
|
35
|
+
duration: 1000,
|
|
36
|
+
})
|
|
37
|
+
state.loading = false
|
|
38
|
+
await nextTick()
|
|
39
|
+
state.loaded = true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const currentMeta = computed(() => {
|
|
43
|
+
return edgeFirebase.user.meta
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
onBeforeMount(() => {
|
|
47
|
+
state.meta = currentMeta.value
|
|
48
|
+
props.metaFields.forEach((field) => {
|
|
49
|
+
if (!(field.field in state.meta)) {
|
|
50
|
+
state.meta[field.field] = field.value
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
watch(currentMeta, async () => {
|
|
56
|
+
state.meta = currentMeta.value
|
|
57
|
+
edgeGlobal.edgeState.changeTracker = {}
|
|
58
|
+
state.loaded = false
|
|
59
|
+
await nextTick()
|
|
60
|
+
state.loaded = true
|
|
61
|
+
})
|
|
62
|
+
const route = useRoute()
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<template>
|
|
66
|
+
<Card class="w-full flex-1 bg-muted/50 mx-auto w-full border-none shadow-none pt-2">
|
|
67
|
+
<slot name="header">
|
|
68
|
+
<edge-menu class="bg-secondary text-foreground rounded-none sticky top-0 py-6">
|
|
69
|
+
<template #start>
|
|
70
|
+
<slot name="header-start">
|
|
71
|
+
<component :is="edgeGlobal.iconFromMenu(route)" class="mr-2" />
|
|
72
|
+
<span class="capitalize">My Profile</span>
|
|
73
|
+
</slot>
|
|
74
|
+
</template>
|
|
75
|
+
<template #center>
|
|
76
|
+
<slot name="header-center">
|
|
77
|
+
<div class="w-full px-6" />
|
|
78
|
+
</slot>
|
|
79
|
+
</template>
|
|
80
|
+
<template #end>
|
|
81
|
+
<slot name="header-end">
|
|
82
|
+
<div />
|
|
83
|
+
</slot>
|
|
84
|
+
</template>
|
|
85
|
+
</edge-menu>
|
|
86
|
+
</slot>
|
|
87
|
+
<CardContent v-if="state.loaded" class="p-3 w-full overflow-y-auto scroll-area">
|
|
88
|
+
<edge-shad-form
|
|
89
|
+
v-model="state.form"
|
|
90
|
+
:schema="props.formSchema"
|
|
91
|
+
@submit="onSubmit"
|
|
92
|
+
>
|
|
93
|
+
<CardContent>
|
|
94
|
+
<div v-for="field in props.metaFields" :key="field.field" class="mb-3">
|
|
95
|
+
<edge-g-input
|
|
96
|
+
v-model="state.meta[field.field]"
|
|
97
|
+
:name="field.field"
|
|
98
|
+
:field-type="field.type"
|
|
99
|
+
:label="field.label"
|
|
100
|
+
parent-tracker-id="profile-settings"
|
|
101
|
+
:hint="field.hint"
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
</CardContent>
|
|
105
|
+
<CardFooter>
|
|
106
|
+
<edge-shad-button
|
|
107
|
+
type="submit"
|
|
108
|
+
:disabled="state.loading"
|
|
109
|
+
class="text-white bg-slate-800 hover:bg-slate-400"
|
|
110
|
+
>
|
|
111
|
+
<Loader2 v-if="state.loading" class="w-4 h-4 mr-2 animate-spin" />
|
|
112
|
+
Save
|
|
113
|
+
</edge-shad-button>
|
|
114
|
+
</CardFooter>
|
|
115
|
+
</edge-shad-form>
|
|
116
|
+
</CardContent>
|
|
117
|
+
</Card>
|
|
118
|
+
</template>
|
|
119
|
+
|
|
120
|
+
<style lang="scss" scoped>
|
|
121
|
+
|
|
122
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { inject } from 'vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
title: {
|
|
6
|
+
type: String,
|
|
7
|
+
default: 'Organization(s)',
|
|
8
|
+
},
|
|
9
|
+
})
|
|
10
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
11
|
+
// const edgeGlobal = inject('edgeGlobal')
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<DropdownMenuLabel class="text-xs text-muted-foreground">
|
|
16
|
+
{{ props.title }}
|
|
17
|
+
</DropdownMenuLabel>
|
|
18
|
+
<edge-org-switcher />
|
|
19
|
+
<DropdownMenuSeparator />
|
|
20
|
+
<DropdownMenuItem>Logout</DropdownMenuItem>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<style lang="scss" scoped>
|
|
24
|
+
|
|
25
|
+
</style>
|
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
// TODO: pass possible roles in prop
|
|
3
|
+
import { toTypedSchema } from '@vee-validate/zod'
|
|
4
|
+
import * as z from 'zod'
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
usersCollectionPath: {
|
|
7
|
+
type: String,
|
|
8
|
+
default: () => `organizations/${edgeGlobal.edgeState.currentOrganization}`,
|
|
9
|
+
},
|
|
10
|
+
metaFields: {
|
|
11
|
+
type: Array,
|
|
12
|
+
default: () => [
|
|
13
|
+
{
|
|
14
|
+
field: 'name',
|
|
15
|
+
type: 'text',
|
|
16
|
+
label: 'Name',
|
|
17
|
+
cols: 12,
|
|
18
|
+
value: '',
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
newUserSchema: {
|
|
23
|
+
type: Object,
|
|
24
|
+
default: () =>
|
|
25
|
+
toTypedSchema(
|
|
26
|
+
z.object({
|
|
27
|
+
meta: z.object({
|
|
28
|
+
name: z
|
|
29
|
+
.string({ required_error: 'Name is required' })
|
|
30
|
+
.min(1, { message: 'Name is required' }),
|
|
31
|
+
email: z
|
|
32
|
+
.string({ required_error: 'Email is required' })
|
|
33
|
+
.email({ message: 'Invalid email address' })
|
|
34
|
+
.min(6, { message: 'Email must be at least 6 characters long' })
|
|
35
|
+
.max(50, { message: 'Email must be less than 50 characters long' }),
|
|
36
|
+
}),
|
|
37
|
+
role: z
|
|
38
|
+
.string({ required_error: 'Role is required' })
|
|
39
|
+
.min(1, { message: 'Role is required' }),
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
},
|
|
43
|
+
updateUserSchema: {
|
|
44
|
+
type: Object,
|
|
45
|
+
default: () =>
|
|
46
|
+
toTypedSchema(
|
|
47
|
+
z.object({
|
|
48
|
+
meta: z.object({
|
|
49
|
+
name: z
|
|
50
|
+
.string({ required_error: 'Name is required' })
|
|
51
|
+
.min(1, { message: 'Name is required' }),
|
|
52
|
+
}),
|
|
53
|
+
role: z
|
|
54
|
+
.string({ required_error: 'Role is required' })
|
|
55
|
+
.min(1, { message: 'Role is required' }),
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
// TODO: If a removed user no longer has roles to any organiztions, need to a create new organization for them with
|
|
61
|
+
// default name of "Personal". This will allow them to continue to use the app.
|
|
62
|
+
|
|
63
|
+
// TODO: Add error/success to join/add organization.
|
|
64
|
+
const route = useRoute()
|
|
65
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
66
|
+
const state = reactive({
|
|
67
|
+
filter: '',
|
|
68
|
+
workingItem: {},
|
|
69
|
+
dialog: false,
|
|
70
|
+
form: false,
|
|
71
|
+
currentTitle: '',
|
|
72
|
+
saveButton: 'Invite User',
|
|
73
|
+
helpers: {
|
|
74
|
+
submits: true,
|
|
75
|
+
},
|
|
76
|
+
deleteDialog: false,
|
|
77
|
+
loading: false,
|
|
78
|
+
newItem: {
|
|
79
|
+
meta: {},
|
|
80
|
+
role: '',
|
|
81
|
+
isTemplate: false,
|
|
82
|
+
},
|
|
83
|
+
loaded: false,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const roleNamesOnly = computed(() => {
|
|
87
|
+
return edgeGlobal.edgeState.userRoles.map((role) => {
|
|
88
|
+
return role.name
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const edgeUsers = toRef(edgeFirebase.state, 'users')
|
|
93
|
+
const users = computed(() => Object.values(edgeUsers.value ?? {}))
|
|
94
|
+
|
|
95
|
+
const WIDTHS = {
|
|
96
|
+
1: 'md:col-span-1',
|
|
97
|
+
2: 'md:col-span-2',
|
|
98
|
+
3: 'md:col-span-3',
|
|
99
|
+
4: 'md:col-span-4',
|
|
100
|
+
5: 'md:col-span-5',
|
|
101
|
+
6: 'md:col-span-6',
|
|
102
|
+
7: 'md:col-span-7',
|
|
103
|
+
8: 'md:col-span-8',
|
|
104
|
+
9: 'md:col-span-9',
|
|
105
|
+
10: 'md:col-span-10',
|
|
106
|
+
11: 'md:col-span-11',
|
|
107
|
+
12: 'md:col-span-12',
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const numColsToTailwind = cols => WIDTHS[cols] || 'md:col-span-12'
|
|
111
|
+
|
|
112
|
+
// Helpers to read/write nested keys like "profile.firstName" on plain objects
|
|
113
|
+
function getByPath(obj, path, fallback = undefined) {
|
|
114
|
+
if (!obj || !path)
|
|
115
|
+
return fallback
|
|
116
|
+
const parts = String(path).split('.')
|
|
117
|
+
let cur = obj
|
|
118
|
+
for (const p of parts) {
|
|
119
|
+
if (cur == null || typeof cur !== 'object' || !(p in cur))
|
|
120
|
+
return fallback
|
|
121
|
+
cur = cur[p]
|
|
122
|
+
}
|
|
123
|
+
return cur
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function setByPath(obj, path, value) {
|
|
127
|
+
if (!obj || !path)
|
|
128
|
+
return
|
|
129
|
+
const parts = String(path).split('.')
|
|
130
|
+
let cur = obj
|
|
131
|
+
for (let i = 0; i < parts.length; i++) {
|
|
132
|
+
const key = parts[i]
|
|
133
|
+
if (i === parts.length - 1) {
|
|
134
|
+
cur[key] = value
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
if (cur[key] == null || typeof cur[key] !== 'object')
|
|
138
|
+
cur[key] = {}
|
|
139
|
+
cur = cur[key]
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const sortedFilteredUsers = computed(() => {
|
|
145
|
+
const filter = state.filter.toLowerCase()
|
|
146
|
+
|
|
147
|
+
const getLastName = (fullName) => {
|
|
148
|
+
if (!fullName)
|
|
149
|
+
return ''
|
|
150
|
+
const parts = fullName.trim().split(/\s+/)
|
|
151
|
+
return parts[parts.length - 1] || ''
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return users.value
|
|
155
|
+
.filter(user => user.meta.name.toLowerCase().includes(filter))
|
|
156
|
+
.sort((a, b) => {
|
|
157
|
+
const lastA = getLastName(a.meta.name).toLowerCase()
|
|
158
|
+
const lastB = getLastName(b.meta.name).toLowerCase()
|
|
159
|
+
return lastA.localeCompare(lastB)
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const adminCount = computed(() => {
|
|
164
|
+
return users.value.filter((item) => {
|
|
165
|
+
return item.roles.find((role) => {
|
|
166
|
+
return role.collectionPath === edgeGlobal.edgeState.organizationDocPath.replaceAll('/', '-') && role.role === 'admin'
|
|
167
|
+
})
|
|
168
|
+
}).length
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const addItem = () => {
|
|
172
|
+
state.saveButton = 'Invite User'
|
|
173
|
+
const newItem = edgeGlobal.dupObject(state.newItem)
|
|
174
|
+
newItem.meta.email = ''
|
|
175
|
+
state.workingItem = newItem
|
|
176
|
+
state.workingItem.id = edgeGlobal.generateShortId()
|
|
177
|
+
state.currentTitle = 'Invite User'
|
|
178
|
+
state.dialog = true
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const editItem = (item) => {
|
|
182
|
+
state.currentTitle = item.meta.name
|
|
183
|
+
state.saveButton = 'Update User'
|
|
184
|
+
state.workingItem = edgeGlobal.dupObject(item)
|
|
185
|
+
state.workingItem.meta = edgeGlobal.dupObject(item.meta)
|
|
186
|
+
state.workingItem.role = edgeGlobal.getRoleName(item.roles, edgeGlobal.edgeState.currentOrganization)
|
|
187
|
+
const newItemKeys = Object.keys(state.newItem)
|
|
188
|
+
newItemKeys.forEach((key) => {
|
|
189
|
+
if (!state.workingItem?.[key]) {
|
|
190
|
+
state.workingItem[key] = state.newItem[key]
|
|
191
|
+
}
|
|
192
|
+
if (key === 'meta') {
|
|
193
|
+
const metaKeys = Object.keys(state.newItem.meta)
|
|
194
|
+
metaKeys.forEach((metaKey) => {
|
|
195
|
+
if (!state.workingItem?.meta?.[metaKey]) {
|
|
196
|
+
state.workingItem.meta[metaKey] = state.newItem.meta[metaKey]
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
console.log('Working Item:', state.workingItem)
|
|
202
|
+
state.dialog = true
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const deleteConfirm = (item) => {
|
|
206
|
+
state.currentTitle = item.name
|
|
207
|
+
state.workingItem = edgeGlobal.dupObject(item)
|
|
208
|
+
state.deleteDialog = true
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const deleteAction = async () => {
|
|
212
|
+
const userRoles = state.workingItem.roles.filter((role) => {
|
|
213
|
+
return role.collectionPath.startsWith(edgeGlobal.edgeState.organizationDocPath.replaceAll('/', '-'))
|
|
214
|
+
})
|
|
215
|
+
for (const role of userRoles) {
|
|
216
|
+
await edgeFirebase.removeUserRoles(state.workingItem.docId, role.collectionPath)
|
|
217
|
+
// console.log(role.collectionPath)
|
|
218
|
+
}
|
|
219
|
+
state.deleteDialog = false
|
|
220
|
+
edgeGlobal.edgeState.changeTracker = {}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const closeDialog = () => {
|
|
224
|
+
state.dialog = false
|
|
225
|
+
edgeGlobal.edgeState.changeTracker = {}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const disableTracking = computed(() => {
|
|
229
|
+
return state.saveButton === 'Invite User'
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
const onSubmit = async () => {
|
|
233
|
+
state.loading = true
|
|
234
|
+
const userRoles = edgeGlobal.orgUserRoles(edgeGlobal.edgeState.currentOrganization)
|
|
235
|
+
const roles = userRoles.find(role => role.name === state.workingItem.role).roles
|
|
236
|
+
if (state.saveButton === 'Invite User') {
|
|
237
|
+
if (!state.workingItem.isTemplate) {
|
|
238
|
+
await edgeFirebase.addUser({ roles, meta: state.workingItem.meta })
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
await edgeFirebase.addUser({ roles, meta: state.workingItem.meta, isTemplate: true })
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
const oldRoles = state.workingItem.roles.filter((role) => {
|
|
246
|
+
return role.collectionPath.startsWith(edgeGlobal.edgeState.organizationDocPath.replaceAll('/', '-'))
|
|
247
|
+
&& !roles.find(r => r.collectionPath === role.collectionPath)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
for (const role of oldRoles) {
|
|
251
|
+
await edgeFirebase.removeUserRoles(state.workingItem.docId, role.collectionPath)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
for (const role of roles) {
|
|
255
|
+
await edgeFirebase.storeUserRoles(state.workingItem.docId, role.collectionPath, role.role)
|
|
256
|
+
}
|
|
257
|
+
const stagedUserId = state.workingItem.docId
|
|
258
|
+
await edgeFirebase.setUserMeta(state.workingItem.meta, '', stagedUserId)
|
|
259
|
+
}
|
|
260
|
+
edgeGlobal.edgeState.changeTracker = {}
|
|
261
|
+
state.loading = false
|
|
262
|
+
state.dialog = false
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const computedUserSchema = computed(() =>
|
|
266
|
+
state.saveButton === 'Invite User'
|
|
267
|
+
? props.newUserSchema
|
|
268
|
+
: props.updateUserSchema,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
const currentOrganization = computed(() => {
|
|
272
|
+
if (edgeGlobal.edgeState.organizations.length > 0) {
|
|
273
|
+
if (edgeGlobal.edgeState.currentOrganization && edgeFirebase?.data[`organizations/${edgeGlobal.edgeState.currentOrganization}`]) {
|
|
274
|
+
return edgeFirebase?.data[`organizations/${edgeGlobal.edgeState.currentOrganization}`]
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return ''
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
onBeforeMount(async () => {
|
|
281
|
+
props.metaFields.forEach((field) => {
|
|
282
|
+
const keys = field.field.split('.')
|
|
283
|
+
let current = state.newItem.meta
|
|
284
|
+
|
|
285
|
+
keys.forEach((key, index) => {
|
|
286
|
+
if (index === keys.length - 1) {
|
|
287
|
+
console.log(`Setting ${key} to ${field.value}`)
|
|
288
|
+
current[key] = field.value
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
if (!current[key]) {
|
|
292
|
+
current[key] = {}
|
|
293
|
+
}
|
|
294
|
+
current = current[key]
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
await edgeFirebase.startUsersSnapshot(props.usersCollectionPath)
|
|
299
|
+
state.loaded = true
|
|
300
|
+
})
|
|
301
|
+
</script>
|
|
302
|
+
|
|
303
|
+
<template>
|
|
304
|
+
<Card v-if="state.loaded" class="w-full flex-1 bg-muted/50 mx-auto w-full border-none shadow-none pt-2">
|
|
305
|
+
<slot name="header" :add-item="addItem">
|
|
306
|
+
<edge-menu class="bg-secondary text-foreground rounded-none sticky top-0 py-6">
|
|
307
|
+
<template #start>
|
|
308
|
+
<slot name="header-start">
|
|
309
|
+
<component :is="edgeGlobal.iconFromMenu(route)" class="mr-2" />
|
|
310
|
+
<span class="capitalize">Members</span>
|
|
311
|
+
</slot>
|
|
312
|
+
</template>
|
|
313
|
+
<template #center>
|
|
314
|
+
<slot name="header-center">
|
|
315
|
+
<div class="w-full px-6" />
|
|
316
|
+
</slot>
|
|
317
|
+
</template>
|
|
318
|
+
<template #end>
|
|
319
|
+
<slot name="header-end" :add-item="addItem">
|
|
320
|
+
<edge-shad-button class="bg-primary mx-2 h-6 text-xs" @click="addItem()">
|
|
321
|
+
Invite Member
|
|
322
|
+
</edge-shad-button>
|
|
323
|
+
</slot>
|
|
324
|
+
</template>
|
|
325
|
+
</edge-menu>
|
|
326
|
+
</slot>
|
|
327
|
+
<CardContent class="p-3 w-full overflow-y-auto scroll-area">
|
|
328
|
+
<Input
|
|
329
|
+
v-model="state.filter"
|
|
330
|
+
class="mb-2"
|
|
331
|
+
placeholder="Filter members..."
|
|
332
|
+
/>
|
|
333
|
+
<div v-if="sortedFilteredUsers.length > 0">
|
|
334
|
+
<div v-for="user in sortedFilteredUsers" :key="user.id" class="flex w-full py-2 justify-between items-center cursor-pointer" @click="editItem(user)">
|
|
335
|
+
<slot name="user" :user="user">
|
|
336
|
+
<Avatar class="handle pointer p-0 h-6 w-6 mr-2">
|
|
337
|
+
<User width="18" height="18" />
|
|
338
|
+
</Avatar>
|
|
339
|
+
<div class="flex gap-2 mr-2 items-center">
|
|
340
|
+
<div class="text-md text-bold mr-2">
|
|
341
|
+
{{ user.meta.name }}
|
|
342
|
+
</div>
|
|
343
|
+
<edge-chip v-if="user.userId === edgeFirebase.user.uid">
|
|
344
|
+
You
|
|
345
|
+
</edge-chip>
|
|
346
|
+
<!-- <edge-chip v-if="!user.userId" class="bg-primary">
|
|
347
|
+
Invited, Not Registered
|
|
348
|
+
</edge-chip> -->
|
|
349
|
+
</div>
|
|
350
|
+
<div class="grow flex gap-2 justify-end">
|
|
351
|
+
<template v-if="!user.userId">
|
|
352
|
+
<edge-chip class="bg-slate-600 w-[200px]">
|
|
353
|
+
{{ user.docId }}
|
|
354
|
+
<edge-clipboard-button class="relative ml-1 top-[2px] mt-0" :text="user.docId" />
|
|
355
|
+
</edge-chip>
|
|
356
|
+
</template>
|
|
357
|
+
<edge-chip>
|
|
358
|
+
{{ edgeGlobal.getRoleName(user.roles, edgeGlobal.edgeState.currentOrganization) }}
|
|
359
|
+
</edge-chip>
|
|
360
|
+
</div>
|
|
361
|
+
<edge-shad-button
|
|
362
|
+
:disabled="users.length === 1"
|
|
363
|
+
class="bg-red-400 mx-2 h-6 w-[80px] text-xs"
|
|
364
|
+
variant="outline"
|
|
365
|
+
@click.stop="deleteConfirm(user)"
|
|
366
|
+
>
|
|
367
|
+
<span v-if="user.userId === edgeFirebase.user.uid">Leave</span>
|
|
368
|
+
<span v-else>Remove</span>
|
|
369
|
+
</edge-shad-button>
|
|
370
|
+
</slot>
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
<edge-shad-dialog
|
|
374
|
+
v-model="state.deleteDialog"
|
|
375
|
+
>
|
|
376
|
+
<DialogContent>
|
|
377
|
+
<DialogHeader>
|
|
378
|
+
<DialogTitle>
|
|
379
|
+
<span v-if="state.workingItem.userId === edgeFirebase.user.uid">
|
|
380
|
+
Remove Yourself?
|
|
381
|
+
</span>
|
|
382
|
+
<span v-else>
|
|
383
|
+
Remove "{{ state.workingItem.meta.name }}"
|
|
384
|
+
</span>
|
|
385
|
+
</DialogTitle>
|
|
386
|
+
<DialogDescription />
|
|
387
|
+
</DialogHeader>
|
|
388
|
+
|
|
389
|
+
<h3 v-if="state.workingItem.userId === edgeFirebase.user.uid && adminCount > 1">
|
|
390
|
+
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.
|
|
391
|
+
</h3>
|
|
392
|
+
<h3 v-else-if="state.workingItem.userId === edgeFirebase.user.uid && adminCount === 1">
|
|
393
|
+
You cannot remove yourself from this organization because you are the only admin. You can delete the organization or add another admin.
|
|
394
|
+
</h3>
|
|
395
|
+
<h3 v-else>
|
|
396
|
+
Are you sure you want to remove "{{ state.workingItem.meta.name }}" from the organization "{{ currentOrganization.name }}"?
|
|
397
|
+
</h3>
|
|
398
|
+
<DialogFooter class="pt-6 flex justify-between">
|
|
399
|
+
<edge-shad-button class="text-white bg-slate-800 hover:bg-slate-400" @click="state.deleteDialog = false">
|
|
400
|
+
Cancel
|
|
401
|
+
</edge-shad-button>
|
|
402
|
+
<edge-shad-button
|
|
403
|
+
:disabled="adminCount === 1 && state.workingItem.userId === edgeFirebase.user.uid"
|
|
404
|
+
class="w-full"
|
|
405
|
+
variant="destructive"
|
|
406
|
+
@click="deleteAction()"
|
|
407
|
+
>
|
|
408
|
+
<span v-if="state.workingItem.userId === edgeFirebase.user.uid">
|
|
409
|
+
Leave
|
|
410
|
+
</span>
|
|
411
|
+
<span v-else>
|
|
412
|
+
Remove
|
|
413
|
+
</span>
|
|
414
|
+
</edge-shad-button>
|
|
415
|
+
</DialogFooter>
|
|
416
|
+
</DialogContent>
|
|
417
|
+
</edge-shad-dialog>
|
|
418
|
+
<edge-shad-dialog
|
|
419
|
+
v-model="state.dialog"
|
|
420
|
+
>
|
|
421
|
+
<DialogContent class="w-full max-w-[1200px]">
|
|
422
|
+
<edge-shad-form :initial-values="state.workingItem" :schema="computedUserSchema" @submit="onSubmit">
|
|
423
|
+
<DialogHeader class="mb-4">
|
|
424
|
+
<DialogTitle>
|
|
425
|
+
{{ state.currentTitle }}
|
|
426
|
+
</DialogTitle>
|
|
427
|
+
<DialogDescription />
|
|
428
|
+
</DialogHeader>
|
|
429
|
+
<slot name="edit-fields" :working-item="state.workingItem">
|
|
430
|
+
<edge-g-input
|
|
431
|
+
v-model="state.workingItem.role"
|
|
432
|
+
name="role"
|
|
433
|
+
:disable-tracking="true"
|
|
434
|
+
:items="roleNamesOnly"
|
|
435
|
+
field-type="select"
|
|
436
|
+
label="Role"
|
|
437
|
+
:parent-tracker-id="`inviteUser-${state.workingItem.id}`"
|
|
438
|
+
:disabled="state.workingItem.userId === edgeFirebase.user.uid"
|
|
439
|
+
/>
|
|
440
|
+
<edge-g-input
|
|
441
|
+
v-if="state.saveButton === 'Invite User'"
|
|
442
|
+
v-model="state.workingItem.meta.email"
|
|
443
|
+
name="meta.email"
|
|
444
|
+
:disable-tracking="true"
|
|
445
|
+
field-type="text"
|
|
446
|
+
label="Email"
|
|
447
|
+
:parent-tracker-id="`inviteUser-${state.workingItem.id}`"
|
|
448
|
+
/>
|
|
449
|
+
<Separator class="my-6" />
|
|
450
|
+
<div class="grid grid-cols-12 gap-2">
|
|
451
|
+
<div v-for="field in props.metaFields" :key="field.field" class="mb-3 col-span-12" :class="numColsToTailwind(field.cols)">
|
|
452
|
+
<!-- Use explicit model binding so dotted paths (e.g., "address.street") work -->
|
|
453
|
+
<edge-g-input
|
|
454
|
+
v-if="field?.type === 'textarea'"
|
|
455
|
+
:model-value="getByPath(state.workingItem.meta, field.field, '')"
|
|
456
|
+
:name="`meta.${field.field}`"
|
|
457
|
+
:field-type="field?.type"
|
|
458
|
+
:label="field?.label"
|
|
459
|
+
parent-tracker-id="user-settings"
|
|
460
|
+
:hint="field?.hint"
|
|
461
|
+
:disable-tracking="true"
|
|
462
|
+
:bindings="{ class: 'h-60' }"
|
|
463
|
+
@update:model-value="val => setByPath(state.workingItem.meta, field.field, val)"
|
|
464
|
+
/>
|
|
465
|
+
<edge-shad-tags
|
|
466
|
+
v-else-if="field?.type === 'tags' || field?.type === 'commaTags'"
|
|
467
|
+
:model-value="getByPath(state.workingItem.meta, field.field, '')"
|
|
468
|
+
:name="`meta.${field.field}`"
|
|
469
|
+
:field-type="field?.type"
|
|
470
|
+
:label="field?.label"
|
|
471
|
+
parent-tracker-id="user-settings"
|
|
472
|
+
:hint="field?.hint"
|
|
473
|
+
:disable-tracking="true"
|
|
474
|
+
@update:model-value="val => setByPath(state.workingItem.meta, field.field, val)"
|
|
475
|
+
/>
|
|
476
|
+
<edge-g-input
|
|
477
|
+
v-else
|
|
478
|
+
:model-value="getByPath(state.workingItem.meta, field.field, '')"
|
|
479
|
+
:name="`meta.${field.field}`"
|
|
480
|
+
:field-type="field?.type"
|
|
481
|
+
:label="field?.label"
|
|
482
|
+
parent-tracker-id="user-settings"
|
|
483
|
+
:hint="field?.hint"
|
|
484
|
+
:disable-tracking="true"
|
|
485
|
+
@update:model-value="val => setByPath(state.workingItem.meta, field.field, val)"
|
|
486
|
+
/>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
|
|
490
|
+
<edge-g-input
|
|
491
|
+
v-if="state.saveButton === 'Invite User'"
|
|
492
|
+
v-model="state.workingItem.isTemplate"
|
|
493
|
+
name="isTemplate"
|
|
494
|
+
:disable-tracking="true"
|
|
495
|
+
field-type="boolean"
|
|
496
|
+
label="Template User"
|
|
497
|
+
:parent-tracker-id="`inviteUser-${state.workingItem.id}`"
|
|
498
|
+
/>
|
|
499
|
+
</slot>
|
|
500
|
+
<DialogFooter class="pt-6 flex justify-between">
|
|
501
|
+
<edge-shad-button variant="destructive" @click="closeDialog">
|
|
502
|
+
Cancel
|
|
503
|
+
</edge-shad-button>
|
|
504
|
+
<edge-shad-button
|
|
505
|
+
:disabled="state.loading"
|
|
506
|
+
class="text-white w-100 bg-slate-800 hover:bg-slate-400"
|
|
507
|
+
type="submit"
|
|
508
|
+
>
|
|
509
|
+
<Loader2 v-if="state.loading" class="w-4 h-4 mr-2 animate-spin" />
|
|
510
|
+
{{ state.saveButton }}
|
|
511
|
+
</edge-shad-button>
|
|
512
|
+
</DialogFooter>
|
|
513
|
+
</edge-shad-form>
|
|
514
|
+
</DialogContent>
|
|
515
|
+
</edge-shad-dialog>
|
|
516
|
+
</CardContent>
|
|
517
|
+
</Card>
|
|
518
|
+
</template>
|
|
519
|
+
|
|
520
|
+
<style lang="scss" scoped>
|
|
521
|
+
|
|
522
|
+
</style>
|