@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.
- package/edge/components/auth/register.vue +51 -0
- package/edge/components/cms/block.vue +363 -42
- package/edge/components/cms/blockEditor.vue +50 -3
- package/edge/components/cms/codeEditor.vue +39 -2
- package/edge/components/cms/htmlContent.vue +10 -2
- package/edge/components/cms/init_blocks/footer.html +111 -19
- package/edge/components/cms/init_blocks/image.html +8 -0
- package/edge/components/cms/init_blocks/post_content.html +3 -2
- package/edge/components/cms/init_blocks/post_title_header.html +8 -6
- package/edge/components/cms/init_blocks/posts_list.html +6 -5
- package/edge/components/cms/mediaCard.vue +13 -2
- package/edge/components/cms/mediaManager.vue +35 -5
- package/edge/components/cms/menu.vue +384 -61
- package/edge/components/cms/optionsSelect.vue +20 -3
- package/edge/components/cms/page.vue +160 -18
- package/edge/components/cms/site.vue +548 -374
- package/edge/components/cms/siteSettingsForm.vue +623 -0
- package/edge/components/cms/themeDefaultMenu.vue +258 -22
- package/edge/components/cms/themeEditor.vue +95 -11
- package/edge/components/editor.vue +1 -0
- package/edge/components/formSubtypes/myOrgs.vue +112 -1
- package/edge/components/imagePicker.vue +126 -0
- package/edge/components/myAccount.vue +1 -0
- package/edge/components/myProfile.vue +345 -61
- package/edge/components/orgSwitcher.vue +1 -1
- package/edge/components/organizationMembers.vue +620 -235
- package/edge/components/shad/html.vue +6 -0
- package/edge/components/shad/number.vue +2 -2
- package/edge/components/sideBar.vue +7 -4
- package/edge/components/sideBarContent.vue +1 -1
- package/edge/components/userMenu.vue +50 -14
- package/edge/composables/global.ts +4 -1
- package/edge/composables/siteSettingsTemplate.js +79 -0
- package/edge/composables/structuredDataTemplates.js +36 -0
- 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
|