@edgedev/create-edge-app 1.1.27 → 1.1.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.
Files changed (35) hide show
  1. package/edge/components/auth/register.vue +51 -0
  2. package/edge/components/cms/block.vue +363 -42
  3. package/edge/components/cms/blockEditor.vue +50 -3
  4. package/edge/components/cms/codeEditor.vue +39 -2
  5. package/edge/components/cms/htmlContent.vue +10 -2
  6. package/edge/components/cms/init_blocks/footer.html +111 -19
  7. package/edge/components/cms/init_blocks/image.html +8 -0
  8. package/edge/components/cms/init_blocks/post_content.html +3 -2
  9. package/edge/components/cms/init_blocks/post_title_header.html +8 -6
  10. package/edge/components/cms/init_blocks/posts_list.html +6 -5
  11. package/edge/components/cms/mediaCard.vue +13 -2
  12. package/edge/components/cms/mediaManager.vue +35 -5
  13. package/edge/components/cms/menu.vue +384 -61
  14. package/edge/components/cms/optionsSelect.vue +20 -3
  15. package/edge/components/cms/page.vue +160 -18
  16. package/edge/components/cms/site.vue +548 -374
  17. package/edge/components/cms/siteSettingsForm.vue +623 -0
  18. package/edge/components/cms/themeDefaultMenu.vue +258 -22
  19. package/edge/components/cms/themeEditor.vue +95 -11
  20. package/edge/components/editor.vue +1 -0
  21. package/edge/components/formSubtypes/myOrgs.vue +112 -1
  22. package/edge/components/imagePicker.vue +126 -0
  23. package/edge/components/myAccount.vue +1 -0
  24. package/edge/components/myProfile.vue +345 -61
  25. package/edge/components/orgSwitcher.vue +1 -1
  26. package/edge/components/organizationMembers.vue +620 -235
  27. package/edge/components/shad/html.vue +6 -0
  28. package/edge/components/shad/number.vue +2 -2
  29. package/edge/components/sideBar.vue +7 -4
  30. package/edge/components/sideBarContent.vue +1 -1
  31. package/edge/components/userMenu.vue +50 -14
  32. package/edge/composables/global.ts +4 -1
  33. package/edge/composables/siteSettingsTemplate.js +79 -0
  34. package/edge/composables/structuredDataTemplates.js +36 -0
  35. package/package.json +1 -1
@@ -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
@@ -0,0 +1,126 @@
1
+ <script setup>
2
+ import { useVModel } from '@vueuse/core'
3
+ import { ImagePlus, Trash2 } from 'lucide-vue-next'
4
+
5
+ const props = defineProps({
6
+ modelValue: {
7
+ type: String,
8
+ default: '',
9
+ },
10
+ label: {
11
+ type: String,
12
+ default: 'Photo',
13
+ },
14
+ dialogTitle: {
15
+ type: String,
16
+ default: 'Select Image',
17
+ },
18
+ site: {
19
+ type: String,
20
+ default: 'clearwater-hub-images',
21
+ },
22
+ defaultTags: {
23
+ type: Array,
24
+ default: () => [],
25
+ },
26
+ includeCmsAll: {
27
+ type: Boolean,
28
+ default: true,
29
+ },
30
+ heightClass: {
31
+ type: String,
32
+ default: 'h-[160px]',
33
+ },
34
+ showRemove: {
35
+ type: Boolean,
36
+ default: true,
37
+ },
38
+ disabled: {
39
+ type: Boolean,
40
+ default: false,
41
+ },
42
+ })
43
+
44
+ const emits = defineEmits(['update:modelValue'])
45
+
46
+ const state = reactive({
47
+ dialog: false,
48
+ })
49
+
50
+ const modelValue = useVModel(props, 'modelValue', emits, {
51
+ passive: false,
52
+ prop: 'modelValue',
53
+ })
54
+
55
+ const openDialog = () => {
56
+ if (props.disabled)
57
+ return
58
+ state.dialog = true
59
+ }
60
+
61
+ const closeDialog = () => {
62
+ state.dialog = false
63
+ }
64
+
65
+ const selectImage = (url) => {
66
+ modelValue.value = url || ''
67
+ closeDialog()
68
+ }
69
+
70
+ const clearImage = () => {
71
+ if (props.disabled)
72
+ return
73
+ modelValue.value = ''
74
+ }
75
+ </script>
76
+
77
+ <template>
78
+ <div class="rounded-lg border bg-background p-4 space-y-3">
79
+ <div class="flex flex-wrap items-center justify-between gap-2">
80
+ <div class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
81
+ {{ props.label }}
82
+ </div>
83
+ <div class="flex items-center gap-2">
84
+ <edge-shad-button size="sm" variant="outline" class="h-8 px-2" :disabled="props.disabled" @click="openDialog">
85
+ <ImagePlus class="h-4 w-4 mr-2" />
86
+ Select
87
+ </edge-shad-button>
88
+ <edge-shad-button
89
+ v-if="props.showRemove && modelValue"
90
+ size="icon"
91
+ variant="ghost"
92
+ class="h-8 w-8 text-destructive/80 hover:text-destructive"
93
+ :disabled="props.disabled"
94
+ @click="clearImage"
95
+ >
96
+ <Trash2 class="h-4 w-4" />
97
+ </edge-shad-button>
98
+ </div>
99
+ </div>
100
+ <div class="rounded-lg flex items-center justify-center overflow-hidden border bg-muted/20" :class="props.heightClass">
101
+ <img
102
+ v-if="modelValue"
103
+ :src="modelValue"
104
+ alt=""
105
+ class="max-h-full max-w-full h-auto w-auto object-contain"
106
+ >
107
+ <span v-else class="text-sm text-muted-foreground italic">No photo selected</span>
108
+ </div>
109
+ </div>
110
+
111
+ <Dialog v-model:open="state.dialog">
112
+ <DialogContent class="w-full max-w-[1200px] max-h-[80vh] overflow-y-auto">
113
+ <DialogHeader>
114
+ <DialogTitle>{{ props.dialogTitle }}</DialogTitle>
115
+ <DialogDescription />
116
+ </DialogHeader>
117
+ <edge-cms-media-manager
118
+ :site="props.site"
119
+ :default-tags="props.defaultTags"
120
+ :include-cms-all="props.includeCmsAll"
121
+ :select-mode="true"
122
+ @select="selectImage"
123
+ />
124
+ </DialogContent>
125
+ </Dialog>
126
+ </template>
@@ -34,6 +34,7 @@ const updateUser = async () => {
34
34
  state.userError = { success: state.userError.success, message: state.userError.message.replace('Firebase: ', '').replace(' (auth/invalid-email)', '') }
35
35
  if (state.userError.success) {
36
36
  state.userError = { success: true, message: 'A verification link has been sent to your new email address. Please click the link to complete the email change process.' }
37
+ await edgeFirebase.setUserMeta({ email: state.username })
37
38
  }
38
39
  edgeGlobal.edgeState.changeTracker = {}
39
40
  state.loaded = false