@edgedev/create-edge-app 1.1.27 → 1.1.28

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.
@@ -30,18 +30,38 @@ const state = reactive({
30
30
  },
31
31
  deleteDialog: false,
32
32
  loading: false,
33
+ bringUserIds: [],
34
+ })
35
+
36
+ const edgeUsers = toRef(edgeFirebase.state, 'users')
37
+ const users = computed(() => Object.values(edgeUsers.value ?? {}))
38
+ const roles = computed(() => edgeFirebase.user.roles)
39
+
40
+ const bringUserOptions = computed(() => {
41
+ return users.value.filter(user => user.userId !== edgeFirebase.user.uid)
42
+ })
43
+
44
+ const showBringUsers = computed(() => {
45
+ return state.saveButton === 'Add Organization' && bringUserOptions.value.length > 0
33
46
  })
34
47
 
35
48
  const newItem = {
36
49
  name: '',
37
50
  }
38
51
 
52
+ const startUsersSnapshot = async () => {
53
+ if (!edgeGlobal.edgeState.currentOrganization)
54
+ return
55
+ await edgeFirebase.startUsersSnapshot(`organizations/${edgeGlobal.edgeState.currentOrganization}`)
56
+ }
57
+
39
58
  const addItem = () => {
40
59
  console.log(newItem)
41
60
  state.saveButton = 'Add Organization'
42
61
  state.workingItem = edgeGlobal.dupObject(newItem)
43
62
  state.workingItem.id = edgeGlobal.generateShortId()
44
63
  state.currentTitle = 'Add Organization'
64
+ state.bringUserIds = []
45
65
  state.dialog = true
46
66
  }
47
67
 
@@ -75,9 +95,77 @@ const register = reactive({
75
95
  dynamicDocumentFieldValue: '',
76
96
  })
77
97
 
98
+ const updateBringUserSelection = (docId, checked) => {
99
+ const selections = new Set(state.bringUserIds)
100
+ if (checked) {
101
+ selections.add(docId)
102
+ }
103
+ else {
104
+ selections.delete(docId)
105
+ }
106
+ state.bringUserIds = Array.from(selections)
107
+ }
108
+
109
+ const addUsersToOrganization = async (orgId) => {
110
+ if (!orgId || state.bringUserIds.length === 0)
111
+ return
112
+ const targetUsers = users.value.filter(user => state.bringUserIds.includes(user.docId))
113
+ for (const user of targetUsers) {
114
+ const roleName = edgeGlobal.getRoleName(user.roles, edgeGlobal.edgeState.currentOrganization)
115
+ const resolvedRoleName = roleName === 'Unknown' ? 'User' : roleName
116
+ const orgRoles = edgeGlobal.orgUserRoles(orgId)
117
+ const roleMatch = orgRoles.find(role => role.name === resolvedRoleName)
118
+ if (!roleMatch)
119
+ continue
120
+ for (const role of roleMatch.roles) {
121
+ await edgeFirebase.storeUserRoles(user.docId, role.collectionPath, role.role)
122
+ }
123
+ }
124
+ }
125
+
126
+ const waitForOrgRole = async (orgId) => {
127
+ const orgPath = `organizations-${String(orgId).replaceAll('/', '-')}`
128
+ const maxAttempts = 30
129
+ const delayMs = 300
130
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
131
+ if (edgeFirebase.user.roles.some(role => role.collectionPath === orgPath)) {
132
+ return true
133
+ }
134
+ await new Promise(resolve => setTimeout(resolve, delayMs))
135
+ }
136
+ return false
137
+ }
138
+
139
+ const waitForNewOrgRole = async (previousOrgPaths) => {
140
+ const maxAttempts = 30
141
+ const delayMs = 300
142
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
143
+ const currentOrgRoles = edgeFirebase.user.roles.filter(role => role.collectionPath.startsWith('organizations-'))
144
+ const newOrgRole = currentOrgRoles.find(role => !previousOrgPaths.includes(role.collectionPath))
145
+ if (newOrgRole) {
146
+ return newOrgRole
147
+ }
148
+ await new Promise(resolve => setTimeout(resolve, delayMs))
149
+ }
150
+ return null
151
+ }
152
+
153
+ onBeforeMount(async () => {
154
+ await startUsersSnapshot()
155
+ })
156
+
157
+ watch(() => edgeGlobal.edgeState.currentOrganization, async (nextOrg, prevOrg) => {
158
+ if (nextOrg && nextOrg !== prevOrg) {
159
+ await startUsersSnapshot()
160
+ }
161
+ })
162
+
78
163
  const onSubmit = async () => {
79
164
  const registerSend = edgeGlobal.dupObject(register)
80
165
  state.loading = true
166
+ const existingOrgPaths = edgeFirebase.user.roles
167
+ .filter(role => role.collectionPath.startsWith('organizations-'))
168
+ .map(role => role.collectionPath)
81
169
  if (state.saveButton === 'Add Organization') {
82
170
  registerSend.dynamicDocumentFieldValue = state.workingItem.name
83
171
  }
@@ -86,7 +174,14 @@ const onSubmit = async () => {
86
174
  registerSend.registrationCode = state.workingItem.name
87
175
  }
88
176
  const results = await edgeFirebase.currentUserRegister(registerSend)
89
- edgeGlobal.getOrganizations(edgeFirebase)
177
+ await edgeGlobal.getOrganizations(edgeFirebase)
178
+ if (state.saveButton === 'Add Organization') {
179
+ const newOrgRole = await waitForNewOrgRole(existingOrgPaths)
180
+ const newOrgId = newOrgRole?.collectionPath?.replace('organizations-', '')
181
+ if (newOrgId) {
182
+ await addUsersToOrganization(newOrgId)
183
+ }
184
+ }
90
185
  console.log(results)
91
186
  edgeGlobal.edgeState.changeTracker = {}
92
187
  state.dialog = false
@@ -189,6 +284,22 @@ const schema = toTypedSchema(z.object({
189
284
  <template v-else>
190
285
  To join an existing organization, please enter the registration code provided by the organization.
191
286
  </template>
287
+ <div v-if="showBringUsers" class="mt-4 w-full">
288
+ <div class="text-sm font-medium text-foreground">
289
+ Users to bring over (you will be added automatically)
290
+ </div>
291
+ <div class="mt-2 w-full flex flex-wrap gap-2">
292
+ <div v-for="user in bringUserOptions" :key="user.docId" class="flex-1 min-w-[220px]">
293
+ <edge-shad-checkbox
294
+ :name="`bring-user-${user.docId}`"
295
+ :model-value="state.bringUserIds.includes(user.docId)"
296
+ @update:model-value="val => updateBringUserSelection(user.docId, val)"
297
+ >
298
+ {{ user.meta.name }}
299
+ </edge-shad-checkbox>
300
+ </div>
301
+ </div>
302
+ </div>
192
303
  <DialogFooter class="pt-6 flex justify-between">
193
304
  <edge-shad-button variant="destructive" @click="closeDialog">
194
305
  Close
@@ -4,7 +4,7 @@ import { inject } from 'vue'
4
4
  const props = defineProps({
5
5
  title: {
6
6
  type: String,
7
- default: 'Organization(s)',
7
+ default: 'Organization',
8
8
  },
9
9
  })
10
10
  const edgeFirebase = inject('edgeFirebase')
@@ -80,6 +80,9 @@ const state = reactive({
80
80
  role: '',
81
81
  isTemplate: false,
82
82
  },
83
+ inviteOrgIds: [],
84
+ editOrgIds: [],
85
+ removeOrgIds: [],
83
86
  loaded: false,
84
87
  })
85
88
 
@@ -92,6 +95,37 @@ const roleNamesOnly = computed(() => {
92
95
  const edgeUsers = toRef(edgeFirebase.state, 'users')
93
96
  const users = computed(() => Object.values(edgeUsers.value ?? {}))
94
97
 
98
+ const orgCollectionPath = orgId => `organizations-${String(orgId).replaceAll('/', '-')}`
99
+
100
+ const adminOrgOptions = computed(() => {
101
+ const orgs = edgeGlobal.edgeState.organizations || []
102
+ const roles = edgeFirebase?.user?.roles || []
103
+ return orgs.filter(org =>
104
+ roles.some(role => role.collectionPath === orgCollectionPath(org.docId) && role.role === 'admin'),
105
+ )
106
+ })
107
+
108
+ const inviteOrgOptions = computed(() => adminOrgOptions.value)
109
+
110
+ const editOrgOptions = computed(() => adminOrgOptions.value)
111
+
112
+ const removeOrgOptions = computed(() => {
113
+ const userRoles = state.workingItem?.roles || []
114
+ return adminOrgOptions.value.filter(org =>
115
+ userRoles.some(role => role.collectionPath.startsWith(orgCollectionPath(org.docId))),
116
+ )
117
+ })
118
+
119
+ const showInviteOrgSelect = computed(() => inviteOrgOptions.value.length > 1)
120
+ const showEditOrgSelect = computed(() => editOrgOptions.value.length > 1)
121
+ const showRemoveOrgSelect = computed(() => removeOrgOptions.value.length > 1)
122
+
123
+ const selfRemoveBlocked = computed(() => {
124
+ return state.workingItem.userId === edgeFirebase.user.uid
125
+ && adminCount.value === 1
126
+ && state.removeOrgIds.includes(edgeGlobal.edgeState.currentOrganization)
127
+ })
128
+
95
129
  const WIDTHS = {
96
130
  1: 'md:col-span-1',
97
131
  2: 'md:col-span-2',
@@ -175,6 +209,8 @@ const addItem = () => {
175
209
  state.workingItem = newItem
176
210
  state.workingItem.id = edgeGlobal.generateShortId()
177
211
  state.currentTitle = 'Invite User'
212
+ state.inviteOrgIds = [edgeGlobal.edgeState.currentOrganization]
213
+ state.editOrgIds = []
178
214
  state.dialog = true
179
215
  }
180
216
 
@@ -184,6 +220,9 @@ const editItem = (item) => {
184
220
  state.workingItem = edgeGlobal.dupObject(item)
185
221
  state.workingItem.meta = edgeGlobal.dupObject(item.meta)
186
222
  state.workingItem.role = edgeGlobal.getRoleName(item.roles, edgeGlobal.edgeState.currentOrganization)
223
+ state.editOrgIds = editOrgOptions.value
224
+ .filter(org => state.workingItem.roles.some(role => role.collectionPath.startsWith(orgCollectionPath(org.docId))))
225
+ .map(org => org.docId)
187
226
  const newItemKeys = Object.keys(state.newItem)
188
227
  newItemKeys.forEach((key) => {
189
228
  if (!state.workingItem?.[key]) {
@@ -205,15 +244,20 @@ const editItem = (item) => {
205
244
  const deleteConfirm = (item) => {
206
245
  state.currentTitle = item.name
207
246
  state.workingItem = edgeGlobal.dupObject(item)
247
+ state.removeOrgIds = [edgeGlobal.edgeState.currentOrganization]
208
248
  state.deleteDialog = true
209
249
  }
210
250
 
211
251
  const deleteAction = async () => {
252
+ const targetUserId = state.workingItem.docId || state.workingItem.userId
253
+ const selectedOrgIds = state.removeOrgIds.length > 0
254
+ ? state.removeOrgIds
255
+ : [edgeGlobal.edgeState.currentOrganization]
212
256
  const userRoles = state.workingItem.roles.filter((role) => {
213
- return role.collectionPath.startsWith(edgeGlobal.edgeState.organizationDocPath.replaceAll('/', '-'))
257
+ return selectedOrgIds.some(orgId => role.collectionPath.startsWith(orgCollectionPath(orgId)))
214
258
  })
215
259
  for (const role of userRoles) {
216
- await edgeFirebase.removeUserRoles(state.workingItem.docId, role.collectionPath)
260
+ await edgeFirebase.removeUserRoles(targetUserId, role.collectionPath)
217
261
  // console.log(role.collectionPath)
218
262
  }
219
263
  state.deleteDialog = false
@@ -229,10 +273,49 @@ const disableTracking = computed(() => {
229
273
  return state.saveButton === 'Invite User'
230
274
  })
231
275
 
276
+ const updateInviteOrgSelection = (orgId, checked) => {
277
+ const selections = new Set(state.inviteOrgIds)
278
+ if (checked) {
279
+ selections.add(orgId)
280
+ }
281
+ else {
282
+ selections.delete(orgId)
283
+ }
284
+ state.inviteOrgIds = Array.from(selections)
285
+ }
286
+
287
+ const updateEditOrgSelection = (orgId, checked) => {
288
+ const selections = new Set(state.editOrgIds)
289
+ if (checked) {
290
+ selections.add(orgId)
291
+ }
292
+ else {
293
+ selections.delete(orgId)
294
+ }
295
+ state.editOrgIds = Array.from(selections)
296
+ }
297
+
298
+ const updateRemoveOrgSelection = (orgId, checked) => {
299
+ const selections = new Set(state.removeOrgIds)
300
+ if (checked) {
301
+ selections.add(orgId)
302
+ }
303
+ else {
304
+ selections.delete(orgId)
305
+ }
306
+ state.removeOrgIds = Array.from(selections)
307
+ }
308
+
232
309
  const onSubmit = async () => {
233
310
  state.loading = true
234
- const userRoles = edgeGlobal.orgUserRoles(edgeGlobal.edgeState.currentOrganization)
235
- const roles = userRoles.find(role => role.name === state.workingItem.role).roles
311
+ const selectedOrgIds = state.inviteOrgIds.length > 0
312
+ ? state.inviteOrgIds
313
+ : [edgeGlobal.edgeState.currentOrganization]
314
+ const roles = selectedOrgIds.flatMap((orgId) => {
315
+ const userRoles = edgeGlobal.orgUserRoles(orgId)
316
+ const roleMatch = userRoles.find(role => role.name === state.workingItem.role)
317
+ return roleMatch ? roleMatch.roles : []
318
+ })
236
319
  if (state.saveButton === 'Invite User') {
237
320
  if (!state.workingItem.isTemplate) {
238
321
  await edgeFirebase.addUser({ roles, meta: state.workingItem.meta })
@@ -242,19 +325,41 @@ const onSubmit = async () => {
242
325
  }
243
326
  }
244
327
  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)
328
+ const targetUserId = state.workingItem.docId || state.workingItem.userId
329
+ const selectedOrgIds = state.editOrgIds
330
+ for (const org of editOrgOptions.value) {
331
+ const orgId = org.docId
332
+ const orgPath = orgCollectionPath(orgId)
333
+ const shouldHave = selectedOrgIds.includes(orgId)
334
+ const existingRoles = state.workingItem.roles.filter(role =>
335
+ role.collectionPath.startsWith(orgPath),
336
+ )
337
+ if (!shouldHave && existingRoles.length > 0) {
338
+ for (const role of existingRoles) {
339
+ await edgeFirebase.removeUserRoles(targetUserId, role.collectionPath)
340
+ }
341
+ continue
342
+ }
343
+ if (shouldHave) {
344
+ const orgRoles = edgeGlobal.orgUserRoles(orgId)
345
+ const roleMatch = orgRoles.find(role => role.name === state.workingItem.role)
346
+ if (!roleMatch)
347
+ continue
348
+ for (const role of existingRoles) {
349
+ if (!roleMatch.roles.some(r => r.collectionPath === role.collectionPath && r.role === role.role)) {
350
+ await edgeFirebase.removeUserRoles(targetUserId, role.collectionPath)
351
+ }
352
+ }
353
+ for (const role of roleMatch.roles) {
354
+ if (!existingRoles.some(r => r.collectionPath === role.collectionPath && r.role === role.role)) {
355
+ await edgeFirebase.storeUserRoles(targetUserId, role.collectionPath, role.role)
356
+ }
357
+ }
358
+ }
256
359
  }
257
360
  const stagedUserId = state.workingItem.docId
361
+ console.log('Staged User ID:', stagedUserId)
362
+ console.log('Updating meta:', state.workingItem.meta)
258
363
  await edgeFirebase.setUserMeta(state.workingItem.meta, '', stagedUserId)
259
364
  }
260
365
  edgeGlobal.edgeState.changeTracker = {}
@@ -386,21 +491,34 @@ onBeforeMount(async () => {
386
491
  <DialogDescription />
387
492
  </DialogHeader>
388
493
 
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">
494
+ <h3 v-if="selfRemoveBlocked">
393
495
  You cannot remove yourself from this organization because you are the only admin. You can delete the organization or add another admin.
394
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>
395
501
  <h3 v-else>
396
- Are you sure you want to remove "{{ state.workingItem.meta.name }}" from the organization "{{ currentOrganization.name }}"?
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>
397
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>
514
+ </div>
515
+ </div>
398
516
  <DialogFooter class="pt-6 flex justify-between">
399
517
  <edge-shad-button class="text-white bg-slate-800 hover:bg-slate-400" @click="state.deleteDialog = false">
400
518
  Cancel
401
519
  </edge-shad-button>
402
520
  <edge-shad-button
403
- :disabled="adminCount === 1 && state.workingItem.userId === edgeFirebase.user.uid"
521
+ :disabled="selfRemoveBlocked"
404
522
  class="w-full"
405
523
  variant="destructive"
406
524
  @click="deleteAction()"
@@ -437,6 +555,38 @@ onBeforeMount(async () => {
437
555
  :parent-tracker-id="`inviteUser-${state.workingItem.id}`"
438
556
  :disabled="state.workingItem.userId === edgeFirebase.user.uid"
439
557
  />
558
+ <div v-if="state.saveButton !== 'Invite User' && showEditOrgSelect" class="mt-4 w-full">
559
+ <div class="text-sm font-medium text-foreground">
560
+ Organizations
561
+ </div>
562
+ <div class="mt-2 w-full flex flex-wrap gap-2">
563
+ <div v-for="org in editOrgOptions" :key="org.docId" class="flex-1 min-w-[220px]">
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)"
568
+ >
569
+ {{ org.name }}
570
+ </edge-shad-checkbox>
571
+ </div>
572
+ </div>
573
+ </div>
574
+ <div v-if="state.saveButton === 'Invite User' && showInviteOrgSelect" class="mt-4 w-full">
575
+ <div class="text-sm font-medium text-foreground">
576
+ Add to organizations
577
+ </div>
578
+ <div class="mt-2 w-full flex flex-wrap gap-2">
579
+ <div v-for="org in inviteOrgOptions" :key="org.docId" class="flex-1 min-w-[220px]">
580
+ <edge-shad-checkbox
581
+ :name="`invite-org-${org.docId}`"
582
+ :model-value="state.inviteOrgIds.includes(org.docId)"
583
+ @update:model-value="val => updateInviteOrgSelection(org.docId, val)"
584
+ >
585
+ {{ org.name }}
586
+ </edge-shad-checkbox>
587
+ </div>
588
+ </div>
589
+ </div>
440
590
  <edge-g-input
441
591
  v-if="state.saveButton === 'Invite User'"
442
592
  v-model="state.workingItem.meta.email"
@@ -163,6 +163,10 @@ const modelValue = useVModel(props, 'modelValue', emits, {
163
163
  })
164
164
 
165
165
  const editor = ref(null)
166
+ const enterKeyHandler = (event) => {
167
+ if (event.key === 'Enter')
168
+ event.stopPropagation()
169
+ }
166
170
  const imageState = reactive({
167
171
  active: false,
168
172
  size: 'medium',
@@ -279,6 +283,7 @@ onMounted(() => {
279
283
  })
280
284
  editor.value.on('selectionUpdate', updateImageState)
281
285
  editor.value.on('transaction', updateImageState)
286
+ editor.value?.view?.dom?.addEventListener('keydown', enterKeyHandler)
282
287
  updateImageState()
283
288
  applyHeightClasses()
284
289
  })
@@ -287,6 +292,7 @@ onBeforeUnmount(() => {
287
292
  if (editor.value) {
288
293
  editor.value.off('selectionUpdate', updateImageState)
289
294
  editor.value.off('transaction', updateImageState)
295
+ editor.value?.view?.dom?.removeEventListener('keydown', enterKeyHandler)
290
296
  editor.value.destroy()
291
297
  }
292
298
  })
@@ -16,7 +16,7 @@ const props = defineProps({
16
16
  },
17
17
  organizationTitle: {
18
18
  type: String,
19
- default: 'Organization(s)',
19
+ default: 'Organization',
20
20
  },
21
21
  menuItems: {
22
22
  type: Array,
@@ -86,7 +86,8 @@ onMounted(() => {
86
86
  try {
87
87
  devOverride.value = localStorage.getItem(DEV_OVERRIDE_KEY) === '1'
88
88
  edgeGlobal.edgeState.devOverride = devOverride.value
89
- } catch (error) {
89
+ }
90
+ catch (error) {
90
91
  console.warn('dev override read failed', error)
91
92
  }
92
93
  })
@@ -97,10 +98,12 @@ const toggleDevOverride = () => {
97
98
  try {
98
99
  if (devOverride.value) {
99
100
  localStorage.setItem(DEV_OVERRIDE_KEY, '1')
100
- } else {
101
+ }
102
+ else {
101
103
  localStorage.removeItem(DEV_OVERRIDE_KEY)
102
104
  }
103
- } catch (error) {
105
+ }
106
+ catch (error) {
104
107
  console.warn('dev override write failed', error)
105
108
  }
106
109
  }
@@ -17,7 +17,7 @@ const props = defineProps({
17
17
  },
18
18
  organizationTitle: {
19
19
  type: String,
20
- default: 'Organization(s)',
20
+ default: 'Organization',
21
21
  },
22
22
  singleOrg: {
23
23
  type: Boolean,
@@ -3,7 +3,7 @@ import { cn } from '@/lib/utils'
3
3
  const props = defineProps({
4
4
  title: {
5
5
  type: String,
6
- default: 'Organization(s)',
6
+ default: 'Organization',
7
7
  },
8
8
  buttonClass: {
9
9
  type: String,
@@ -26,6 +26,20 @@ const isAdmin = computed(() => {
26
26
 
27
27
  return orgRole && orgRole.role === 'admin'
28
28
  })
29
+ const organizations = computed(() => edgeGlobal.edgeState.organizations || [])
30
+ const currentOrg = computed(() => organizations.value.find(org => org.docId === edgeGlobal.edgeState.currentOrganization))
31
+ const currentOrgName = computed(() => currentOrg.value?.name || 'Organization')
32
+ const hasMultipleOrgs = computed(() => organizations.value.length > 1)
33
+ const orgDialogOpen = ref(false)
34
+ const openOrgDialog = () => {
35
+ if (hasMultipleOrgs.value) {
36
+ orgDialogOpen.value = true
37
+ }
38
+ }
39
+ const selectOrg = (orgId) => {
40
+ edgeGlobal.setOrganization(orgId, edgeFirebase)
41
+ orgDialogOpen.value = false
42
+ }
29
43
  const route = useRoute()
30
44
  const router = useRouter()
31
45
  const goTo = (path) => {
@@ -69,19 +83,16 @@ const firstPart = computed(() => {
69
83
  <DropdownMenuLabel v-if="!props.singleOrg" class="text-xs text-muted-foreground">
70
84
  {{ props.title }}
71
85
  </DropdownMenuLabel>
72
- <template v-for="org in edgeGlobal.edgeState.organizations">
73
- <DropdownMenuItem
74
- v-if="!props.singleOrg"
75
- :key="org.docId"
76
- class="cursor-pointer"
77
- :class="{ 'bg-accent': org.docId === edgeGlobal.edgeState.currentOrganization }"
78
- @click="edgeGlobal.setOrganization(org.docId, edgeFirebase)"
79
- >
80
- {{ org.name }}
81
- <Check v-if="org.docId === edgeGlobal.edgeState.currentOrganization" class="h-3 w-3 mr-2 ml-auto" />
82
- <div v-else class="h-3 w-3 mr-2" />
83
- </DropdownMenuItem>
84
- </template>
86
+ <DropdownMenuItem
87
+ v-if="!props.singleOrg"
88
+ class="cursor-pointer"
89
+ :disabled="!hasMultipleOrgs"
90
+ @click="openOrgDialog"
91
+ >
92
+ <span class="truncate max-w-[180px]">{{ currentOrgName }}</span>
93
+ <span v-if="hasMultipleOrgs" class="ml-auto text-xs text-muted-foreground">Switch</span>
94
+ <ChevronsUpDown v-if="hasMultipleOrgs" class="h-4 w-4 ml-2" />
95
+ </DropdownMenuItem>
85
96
  <template v-if="isAdmin">
86
97
  <DropdownMenuSeparator />
87
98
  <DropdownMenuLabel class="text-xs text-muted-foreground">
@@ -145,4 +156,29 @@ const firstPart = computed(() => {
145
156
  </DropdownMenuItem>
146
157
  </DropdownMenuContent>
147
158
  </DropdownMenu>
159
+
160
+ <edge-shad-dialog v-model="orgDialogOpen">
161
+ <DialogContent class="w-full max-w-3xl max-h-[80vh] flex flex-col">
162
+ <DialogHeader>
163
+ <DialogTitle>Select Organization</DialogTitle>
164
+ <DialogDescription class="text-left">
165
+ Choose an organization to switch context.
166
+ </DialogDescription>
167
+ </DialogHeader>
168
+ <div class="mt-4 flex-1 overflow-y-auto">
169
+ <div class="space-y-2">
170
+ <edge-shad-button
171
+ v-for="org in organizations"
172
+ :key="org.docId"
173
+ variant="ghost"
174
+ class="w-full justify-between"
175
+ @click="selectOrg(org.docId)"
176
+ >
177
+ <span class="truncate text-left">{{ org.name }}</span>
178
+ <Check v-if="org.docId === edgeGlobal.edgeState.currentOrganization" class="h-4 w-4" />
179
+ </edge-shad-button>
180
+ </div>
181
+ </div>
182
+ </DialogContent>
183
+ </edge-shad-dialog>
148
184
  </template>