@edgedev/create-edge-app 1.1.25 → 1.1.27

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.
Files changed (111) hide show
  1. package/README.md +55 -20
  2. package/{agent.md → agents.md} +2 -0
  3. package/bin/cli.js +6 -6
  4. package/edge/components/auth/login.vue +384 -0
  5. package/edge/components/auth/register.vue +396 -0
  6. package/edge/components/auth.vue +108 -0
  7. package/edge/components/autoFileUpload.vue +215 -0
  8. package/edge/components/billing.vue +8 -0
  9. package/edge/components/buttonDivider.vue +14 -0
  10. package/edge/components/chip.vue +34 -0
  11. package/edge/components/clipboardButton.vue +42 -0
  12. package/edge/components/cms/block.vue +529 -0
  13. package/edge/components/cms/blockApi.vue +212 -0
  14. package/edge/components/cms/blockEditor.vue +725 -0
  15. package/edge/components/cms/blockInput.vue +66 -0
  16. package/edge/components/cms/blockPicker.vue +486 -0
  17. package/edge/components/cms/blockRender.vue +78 -0
  18. package/edge/components/cms/blockSheetContent.vue +28 -0
  19. package/edge/components/cms/codeEditor.vue +466 -0
  20. package/edge/components/cms/fontUpload.vue +327 -0
  21. package/edge/components/cms/htmlContent.vue +807 -0
  22. package/edge/components/cms/init_blocks/api_with_subarrays.html +17 -0
  23. package/edge/components/cms/init_blocks/array_with_collection.html +7 -0
  24. package/edge/components/cms/init_blocks/array_with_objects.html +7 -0
  25. package/edge/components/cms/init_blocks/carousel.html +103 -0
  26. package/edge/components/cms/init_blocks/contact_us.html +69 -0
  27. package/edge/components/cms/init_blocks/content_with_left_image.html +27 -0
  28. package/edge/components/cms/init_blocks/footer.html +24 -0
  29. package/edge/components/cms/init_blocks/header_divider.html +7 -0
  30. package/edge/components/cms/init_blocks/hero.html +35 -0
  31. package/edge/components/cms/init_blocks/hero_carousel.html +52 -0
  32. package/edge/components/cms/init_blocks/newsletter.html +117 -0
  33. package/edge/components/cms/init_blocks/post_content.html +7 -0
  34. package/edge/components/cms/init_blocks/post_title_header.html +21 -0
  35. package/edge/components/cms/init_blocks/posts_list.html +20 -0
  36. package/edge/components/cms/init_blocks/properties_showcase.html +100 -0
  37. package/edge/components/cms/init_blocks/property_carousel.html +59 -0
  38. package/edge/components/cms/init_blocks/property_detail.html +112 -0
  39. package/edge/components/cms/init_blocks/property_detail_header.html +34 -0
  40. package/edge/components/cms/init_blocks/property_results.html +137 -0
  41. package/edge/components/cms/init_blocks/property_search.html +75 -0
  42. package/edge/components/cms/init_blocks/simple_array.html +7 -0
  43. package/edge/components/cms/mediaCard.vue +116 -0
  44. package/edge/components/cms/mediaManager.vue +386 -0
  45. package/edge/components/cms/menu.vue +1103 -0
  46. package/edge/components/cms/optionsSelect.vue +107 -0
  47. package/edge/components/cms/page.vue +1785 -0
  48. package/edge/components/cms/posts.vue +1083 -0
  49. package/edge/components/cms/site.vue +1475 -0
  50. package/edge/components/cms/themeDefaultMenu.vue +548 -0
  51. package/edge/components/cms/themeEditor.vue +429 -0
  52. package/edge/components/dashboard.vue +776 -0
  53. package/edge/components/editor.vue +671 -0
  54. package/edge/components/fileTree.vue +72 -0
  55. package/edge/components/files.vue +89 -0
  56. package/edge/components/formSubtypes/myOrgs.vue +214 -0
  57. package/edge/components/formSubtypes/users.vue +336 -0
  58. package/edge/components/functionChips.vue +57 -0
  59. package/edge/components/gError.vue +98 -0
  60. package/edge/components/gHelper.vue +67 -0
  61. package/edge/components/gInput.vue +1331 -0
  62. package/edge/components/loggingIn.vue +41 -0
  63. package/edge/components/menu.vue +137 -0
  64. package/edge/components/menuContent.vue +132 -0
  65. package/edge/components/myAccount.vue +317 -0
  66. package/edge/components/myOrganizations.vue +75 -0
  67. package/edge/components/myProfile.vue +122 -0
  68. package/edge/components/orgSwitcher.vue +25 -0
  69. package/edge/components/organizationMembers.vue +522 -0
  70. package/edge/components/organizationSettings.vue +271 -0
  71. package/edge/components/shad/breadcrumbs.vue +35 -0
  72. package/edge/components/shad/button.vue +43 -0
  73. package/edge/components/shad/checkbox.vue +73 -0
  74. package/edge/components/shad/combobox.vue +238 -0
  75. package/edge/components/shad/datepicker.vue +184 -0
  76. package/edge/components/shad/dialog.vue +32 -0
  77. package/edge/components/shad/dropdownMenu.vue +54 -0
  78. package/edge/components/shad/dropdownMenuItem.vue +21 -0
  79. package/edge/components/shad/form.vue +59 -0
  80. package/edge/components/shad/html.vue +877 -0
  81. package/edge/components/shad/input.vue +139 -0
  82. package/edge/components/shad/number.vue +109 -0
  83. package/edge/components/shad/select.vue +151 -0
  84. package/edge/components/shad/selectTags.vue +278 -0
  85. package/edge/components/shad/switch.vue +67 -0
  86. package/edge/components/shad/tags.vue +137 -0
  87. package/edge/components/shad/textarea.vue +102 -0
  88. package/edge/components/shad/typeMoney.vue +167 -0
  89. package/edge/components/sideBar.vue +288 -0
  90. package/edge/components/sideBarContent.vue +268 -0
  91. package/edge/components/sidebarProvider.vue +33 -0
  92. package/edge/components/tooltip.vue +16 -0
  93. package/edge/components/userMenu.vue +148 -0
  94. package/edge/components/v/alert.vue +59 -0
  95. package/edge/components/v/alertTitle.vue +18 -0
  96. package/edge/components/v/card.vue +53 -0
  97. package/edge/components/v/cardActions.vue +18 -0
  98. package/edge/components/v/cardText.vue +18 -0
  99. package/edge/components/v/cardTitle.vue +20 -0
  100. package/edge/components/v/col.vue +56 -0
  101. package/edge/components/v/list.vue +46 -0
  102. package/edge/components/v/listItem.vue +26 -0
  103. package/edge/components/v/listItemTitle.vue +18 -0
  104. package/edge/components/v/row.vue +42 -0
  105. package/edge/components/v/toolbar.vue +24 -0
  106. package/edge/composables/global.ts +519 -0
  107. package/edge-pull.sh +2 -0
  108. package/edge-push.sh +1 -0
  109. package/edge-status.sh +14 -0
  110. package/package.json +1 -1
  111. 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>