@edgedev/create-edge-app 1.2.29 → 1.2.30

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.
@@ -7,15 +7,7 @@ const state = reactive({
7
7
  themesFilter: [],
8
8
  })
9
9
 
10
- const previewState = reactive({
11
- loaded: [],
12
- })
13
-
14
- definePageMeta({
15
- middleware: 'auth',
16
- })
17
-
18
- const rawInitBlockFiles = import.meta.glob('../../../../edge/components/cms/init_blocks/*.html', {
10
+ const rawInitBlockFiles = import.meta.glob('./init_blocks/*.html', {
19
11
  as: 'raw',
20
12
  eager: true,
21
13
  })
@@ -74,14 +66,6 @@ const seedInitialBlocks = async () => {
74
66
  return created
75
67
  }
76
68
 
77
- onBeforeMount(async () => {
78
- restoreFilters()
79
- if (!edgeFirebase.data?.[`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`]) {
80
- await edgeFirebase.startSnapshot(`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`)
81
- }
82
- state.mounted = true
83
- })
84
-
85
69
  const getThemeFromId = (themeId) => {
86
70
  const theme = edgeFirebase.data[`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`]?.[themeId]
87
71
  console.log('getThemeFromId', themeId, theme.name)
@@ -93,13 +77,6 @@ const loadingRender = (content) => {
93
77
  return safeContent.replaceAll('{{loading}}', '').replaceAll('{{loaded}}', 'hidden')
94
78
  }
95
79
 
96
- const markPreviewLoaded = (isLoading, id) => {
97
- if (!isLoading && !previewState.loaded.includes(id))
98
- previewState.loaded.push(id)
99
- }
100
-
101
- const hasPreviewLoaded = id => previewState.loaded.includes(id)
102
-
103
80
  const FILTER_STORAGE_KEY = 'edge.blocks.filters'
104
81
 
105
82
  const restoreFilters = () => {
@@ -136,6 +113,14 @@ watch(
136
113
  { deep: true },
137
114
  )
138
115
 
116
+ onBeforeMount(async () => {
117
+ restoreFilters()
118
+ if (!edgeFirebase.data?.[`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`]) {
119
+ await edgeFirebase.startSnapshot(`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`)
120
+ }
121
+ state.mounted = true
122
+ })
123
+
139
124
  const tagOptions = computed(() => {
140
125
  const tagsSet = new Set()
141
126
  const blocks = edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/blocks`] || {}
@@ -0,0 +1,110 @@
1
+ <script setup>
2
+ import { Loader2 } from 'lucide-vue-next'
3
+
4
+ const edgeFirebase = inject('edgeFirebase')
5
+ const isAiBusy = status => status === 'queued' || status === 'running'
6
+
7
+ const state = reactive({
8
+ filter: '',
9
+ })
10
+
11
+ const isAdmin = computed(() => {
12
+ return edgeGlobal.isAdminGlobal(edgeFirebase).value
13
+ })
14
+
15
+ const disableAddSiteForNonAdmin = true
16
+
17
+ const currentOrgRoleName = computed(() => {
18
+ return String(edgeGlobal.getRoleName(edgeFirebase?.user?.roles || [], edgeGlobal.edgeState.currentOrganization) || '').toLowerCase()
19
+ })
20
+
21
+ const canAddSite = computed(() => {
22
+ if (!disableAddSiteForNonAdmin)
23
+ return true
24
+ return currentOrgRoleName.value === 'admin'
25
+ })
26
+
27
+ const queryField = computed(() => {
28
+ if (!isAdmin.value) {
29
+ return 'users'
30
+ }
31
+ return ''
32
+ })
33
+
34
+ const queryValue = computed(() => {
35
+ if (!isAdmin.value) {
36
+ return [edgeFirebase?.user?.uid]
37
+ }
38
+ return ''
39
+ })
40
+
41
+ const queryOperator = computed(() => {
42
+ if (!isAdmin.value) {
43
+ return 'array-contains-any'
44
+ }
45
+ return ''
46
+ })
47
+ </script>
48
+
49
+ <template>
50
+ <edge-dashboard
51
+ :filter="state.filter"
52
+ :query-field="queryField"
53
+ :query-value="queryValue"
54
+ :query-operator="queryOperator"
55
+ collection="sites"
56
+ class="flex-1 pt-0"
57
+ >
58
+ <template #header-end>
59
+ <edge-shad-button
60
+ v-if="canAddSite"
61
+ class="uppercase bg-primary"
62
+ to="/app/dashboard/sites/new"
63
+ >
64
+ Add Site
65
+ </edge-shad-button>
66
+ <div v-else class="hidden" />
67
+ </template>
68
+ <template #list="slotProps">
69
+ <template v-for="item in slotProps.filtered" :key="item.docId">
70
+ <edge-shad-button
71
+ variant="text"
72
+ class="cursor-pointer w-full flex justify-between items-center py-2 gap-3"
73
+ :to="isAiBusy(item.aiBootstrapStatus) ? undefined : `/app/dashboard/sites/${item.docId}`"
74
+ :disabled="isAiBusy(item.aiBootstrapStatus)"
75
+ >
76
+ <div>
77
+ <Avatar class="cursor-pointer p-0 h-8 w-8 mr-2">
78
+ <FilePenLine class="h-5 w-5" />
79
+ </Avatar>
80
+ </div>
81
+ <div class="grow text-left">
82
+ <div class="text-lg">
83
+ {{ item.name }}
84
+ </div>
85
+ <div v-if="isAiBusy(item.aiBootstrapStatus)" class="flex items-center gap-2 text-xs text-muted-foreground">
86
+ <Loader2 class="h-3 w-3 animate-spin" />
87
+ <span>AI is preparing this site</span>
88
+ </div>
89
+ </div>
90
+ <div>
91
+ <edge-shad-button
92
+ size="icon"
93
+ class="bg-slate-600 h-7 w-7"
94
+ @click.stop="slotProps.deleteItem(item.docId)"
95
+ >
96
+ <Trash class="h-5 w-5" />
97
+ </edge-shad-button>
98
+ </div>
99
+ </edge-shad-button>
100
+ <Separator class="dark:bg-slate-600" />
101
+ </template>
102
+ <div
103
+ v-if="slotProps.filtered.length === 0 && !isAdmin && disableAddSiteForNonAdmin"
104
+ class="px-4 py-6 text-sm text-muted-foreground"
105
+ >
106
+ No sites are assigned to your account. Contact an organization admin to add a site for you.
107
+ </div>
108
+ </template>
109
+ </edge-dashboard>
110
+ </template>
@@ -96,6 +96,46 @@ const state = reactive({
96
96
  const edgeFirebase = inject('edgeFirebase')
97
97
  // const edgeGlobal = inject('edgeGlobal')
98
98
 
99
+ const normalizeObject = (value) => {
100
+ if (Array.isArray(value)) {
101
+ return value.map(item => normalizeObject(item))
102
+ }
103
+
104
+ if (value && typeof value === 'object') {
105
+ return Object.keys(value)
106
+ .sort()
107
+ .reduce((acc, key) => {
108
+ acc[key] = normalizeObject(value[key])
109
+ return acc
110
+ }, {})
111
+ }
112
+
113
+ return value
114
+ }
115
+
116
+ const withSchemaDefaults = (doc = {}) => {
117
+ const normalizedDoc = edgeGlobal.dupObject(doc || {})
118
+ Object.keys(newDoc.value).forEach((field) => {
119
+ if (!edgeGlobal.objHas(normalizedDoc, field)) {
120
+ normalizedDoc[field] = newDoc.value[field]
121
+ }
122
+ })
123
+ return normalizedDoc
124
+ }
125
+
126
+ const comparableDoc = (doc = {}) => {
127
+ const sourceDoc = (doc && typeof doc === 'object') ? doc : {}
128
+ const comparable = {}
129
+ Object.keys(props.newDocSchema || {}).forEach((field) => {
130
+ if (edgeGlobal.objHas(sourceDoc, field)) {
131
+ comparable[field] = sourceDoc[field]
132
+ return
133
+ }
134
+ comparable[field] = newDoc.value[field]
135
+ })
136
+ return normalizeObject(comparable)
137
+ }
138
+
99
139
  const unsavedChanges = computed(() => {
100
140
  if (props.docId === 'new') {
101
141
  return false
@@ -107,9 +147,9 @@ const unsavedChanges = computed(() => {
107
147
  return false
108
148
  }
109
149
 
110
- console.log('comparing', state.workingDoc, baselineDoc)
111
- console.log('unsavedChanges', JSON.stringify(state.workingDoc) !== JSON.stringify(baselineDoc))
112
- return JSON.stringify(state.workingDoc) !== JSON.stringify(baselineDoc)
150
+ const workingComparable = comparableDoc(state.workingDoc)
151
+ const baselineComparable = comparableDoc(baselineDoc)
152
+ return JSON.stringify(workingComparable) !== JSON.stringify(baselineComparable)
113
153
  })
114
154
 
115
155
  onBeforeRouteLeave((to, from, next) => {
@@ -164,7 +204,7 @@ const discardChanges = async () => {
164
204
  }
165
205
  return
166
206
  }
167
- state.workingDoc = await edgeGlobal.dupObject(state.collectionData[props.docId])
207
+ state.workingDoc = withSchemaDefaults(state.collectionData[props.docId])
168
208
  state.bypassUnsavedChanges = true
169
209
  state.dialog = false
170
210
  edgeGlobal.edgeState.changeTracker = {}
@@ -328,44 +368,63 @@ const initData = (newVal) => {
328
368
  if (edgeGlobal.objHas(newVal, props.docId) === false) {
329
369
  return
330
370
  }
331
- state.workingDoc = edgeGlobal.dupObject(newVal[props.docId])
332
- Object.keys(newDoc.value).forEach((field) => {
333
- if (!edgeGlobal.objHas(state.workingDoc, field)) {
334
- state.workingDoc[field] = newDoc.value[field]
335
- }
336
- })
371
+ state.workingDoc = withSchemaDefaults(newVal[props.docId])
337
372
  state.afterMount = true
338
373
  }
339
374
  else {
340
375
  if (!state.afterMount) {
341
- state.workingDoc = edgeGlobal.dupObject(newDoc.value)
376
+ state.workingDoc = withSchemaDefaults(newDoc.value)
342
377
  }
343
378
  state.afterMount = true
344
379
  }
345
380
  }
346
381
 
347
- onBeforeMount(async () => {
348
- state.bypassUnsavedChanges = false
349
- edgeGlobal.edgeState.changeTracker = {}
350
- for (const field of Object.keys(props.newDocSchema)) {
382
+ const startCollectionSnapshots = async () => {
383
+ for (const field of Object.keys(props.newDocSchema || {})) {
351
384
  if (props.newDocSchema[field].type === 'collection') {
352
385
  await edgeFirebase.startSnapshot(`${edgeGlobal.edgeState.organizationDocPath}/${field}`)
353
386
  }
354
387
  }
355
- emit('unsavedChanges', unsavedChanges.value)
356
- // console.log('mounting editor for', props.collection, props.docId)
357
- // console.log('starting snapshot for collection:', props.collection)
358
- // await edgeFirebase.startSnapshot(`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`)
359
- if (!edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`]) {
360
- console.log(`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`)
361
- console.log(props.docId)
362
- const docData = await edgeFirebase.getDocData(`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`, props.docId)
363
- state.collectionData[props.docId] = docData
364
- initData(state.collectionData)
365
- }
366
- else {
367
- state.collectionData = edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`]
388
+ }
389
+
390
+ const setCollectionData = async () => {
391
+ const collectionPath = `${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`
392
+ if (!edgeFirebase.data?.[collectionPath]) {
393
+ if (props.docId !== 'new') {
394
+ const docData = await edgeFirebase.getDocData(collectionPath, props.docId)
395
+ state.collectionData = {
396
+ ...(state.collectionData || {}),
397
+ [props.docId]: docData,
398
+ }
399
+ }
400
+ else if (!state.collectionData || typeof state.collectionData !== 'object') {
401
+ state.collectionData = {}
402
+ }
403
+ return
368
404
  }
405
+ state.collectionData = edgeFirebase.data[collectionPath]
406
+ }
407
+
408
+ const resetEditorState = () => {
409
+ state.bypassUnsavedChanges = false
410
+ state.bypassRoute = ''
411
+ state.afterMount = false
412
+ state.dialog = false
413
+ state.successMessage = ''
414
+ state.skipNextValidation = props.docId === 'new'
415
+ edgeGlobal.edgeState.changeTracker = {}
416
+ }
417
+
418
+ const refreshEditorData = async () => {
419
+ resetEditorState()
420
+ await startCollectionSnapshots()
421
+ await setCollectionData()
422
+ initData(state.collectionData)
423
+ emit('unsavedChanges', unsavedChanges.value)
424
+ }
425
+
426
+ onBeforeMount(async () => {
427
+ await refreshEditorData()
369
428
  if (props.noCloseAfterSave) {
370
429
  state.overrideClose = true
371
430
  }
@@ -374,6 +433,17 @@ onBeforeMount(async () => {
374
433
  watch(() => state.collectionData, (newVal) => {
375
434
  initData(newVal)
376
435
  })
436
+
437
+ watch(() => [props.collection, props.docId], async (newVal, oldVal) => {
438
+ if (!oldVal) {
439
+ return
440
+ }
441
+ if (newVal[0] === oldVal[0] && newVal[1] === oldVal[1]) {
442
+ return
443
+ }
444
+ await refreshEditorData()
445
+ })
446
+
377
447
  onActivated(() => {
378
448
  // console.log('activated')
379
449
  state.bypassUnsavedChanges = false
@@ -384,17 +454,12 @@ onActivated(() => {
384
454
  if (edgeGlobal.objHas(state.collectionData, props.docId) === false) {
385
455
  return
386
456
  }
387
- state.workingDoc = edgeGlobal.dupObject(state.collectionData[props.docId])
388
- Object.keys(newDoc.value).forEach((field) => {
389
- if (!edgeGlobal.objHas(state.workingDoc, field)) {
390
- state.workingDoc[field] = newDoc.value[field]
391
- }
392
- })
457
+ state.workingDoc = withSchemaDefaults(state.collectionData[props.docId])
393
458
 
394
459
  // console.log('state.workingDoc', state.workingDoc)
395
460
  }
396
461
  else {
397
- state.workingDoc = edgeGlobal.dupObject(newDoc.value)
462
+ state.workingDoc = withSchemaDefaults(newDoc.value)
398
463
  Object.entries(route.query).forEach(([key, value]) => {
399
464
  // Check if the key exists in state.workingDoc, and if so, set the value
400
465
  if (key in state.workingDoc) {
@@ -0,0 +1,21 @@
1
+ <script setup>
2
+ const state = reactive({
3
+ mounted: false,
4
+ })
5
+
6
+ definePageMeta({
7
+ middleware: 'auth',
8
+ })
9
+
10
+ onMounted(() => {
11
+ state.mounted = true
12
+ })
13
+ </script>
14
+
15
+ <template>
16
+ <div
17
+ v-if="edgeGlobal.edgeState.organizationDocPath && state.mounted"
18
+ >
19
+ <edge-cms-blocks-manager />
20
+ </div>
21
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup>
2
+ definePageMeta({
3
+ middleware: 'auth',
4
+ })
5
+ </script>
6
+
7
+ <template>
8
+ <div
9
+ v-if="edgeGlobal.edgeState.organizationDocPath"
10
+ >
11
+ <edge-cms-sites-manager />
12
+ </div>
13
+ </template>
@@ -0,0 +1,52 @@
1
+ import { resolve as resolvePath } from 'node:path'
2
+ import { cmsRoutes } from './routes'
3
+
4
+ const resolveRouteFileFromCwd = (file) => {
5
+ const normalizedPath = String(file || '').replace(/^\.\//, '')
6
+ return resolvePath(process.cwd(), normalizedPath)
7
+ }
8
+
9
+ export const createCmsNuxtHooks = (resolveFile) => {
10
+ const resolveRouteFile = typeof resolveFile === 'function'
11
+ ? resolveFile
12
+ : resolveRouteFileFromCwd
13
+
14
+ const routeExists = (routeList, name) => {
15
+ for (const route of routeList) {
16
+ if (route.name === name)
17
+ return true
18
+ if (route.children?.length && routeExists(route.children, name))
19
+ return true
20
+ }
21
+ return false
22
+ }
23
+
24
+ const resolveCmsRoute = (route, appRootRoute, isChild = false) => {
25
+ const shouldStripAppPrefix = Boolean(appRootRoute) && !isChild
26
+ const nextRoute = {
27
+ ...route,
28
+ path: shouldStripAppPrefix
29
+ ? route.path.replace(/^\/app\/?/, '')
30
+ : route.path,
31
+ file: resolveRouteFile(route.file),
32
+ }
33
+
34
+ if (route.children?.length)
35
+ nextRoute.children = route.children.map(child => resolveCmsRoute(child, appRootRoute, true))
36
+
37
+ return nextRoute
38
+ }
39
+
40
+ return {
41
+ 'pages:extend': (pages) => {
42
+ const appRootRoute = pages.find(page => page.path === '/app')
43
+ const targetRoutes = appRootRoute?.children || pages
44
+
45
+ cmsRoutes.forEach((route) => {
46
+ const routeToAdd = resolveCmsRoute(route, appRootRoute)
47
+ if (!routeExists(targetRoutes, routeToAdd.name))
48
+ targetRoutes.push(routeToAdd)
49
+ })
50
+ },
51
+ }
52
+ }
@@ -0,0 +1,56 @@
1
+ export const cmsRoutes = [
2
+ {
3
+ name: 'cms-dashboard-sites-index',
4
+ path: '/app/dashboard/sites',
5
+ file: './edge/routes/cms/dashboard/sites/index.vue',
6
+ },
7
+ {
8
+ name: 'cms-dashboard-blocks-index',
9
+ path: '/app/dashboard/blocks',
10
+ file: './edge/routes/cms/dashboard/blocks/index.vue',
11
+ },
12
+ {
13
+ name: 'cms-dashboard-blocks-block',
14
+ path: '/app/dashboard/blocks/:block',
15
+ file: './edge/routes/cms/dashboard/blocks/[block].vue',
16
+ },
17
+ {
18
+ name: 'cms-dashboard-media-index',
19
+ path: '/app/dashboard/media',
20
+ file: './edge/routes/cms/dashboard/media/index.vue',
21
+ },
22
+ {
23
+ name: 'cms-dashboard-sites-site',
24
+ path: '/app/dashboard/sites/:site',
25
+ file: './edge/routes/cms/dashboard/sites/[site].vue',
26
+ children: [
27
+ {
28
+ name: 'cms-dashboard-sites-site-page',
29
+ path: ':page',
30
+ file: './edge/routes/cms/dashboard/sites/[site]/[[page]].vue',
31
+ },
32
+ ],
33
+ },
34
+ {
35
+ name: 'cms-dashboard-templates-index',
36
+ path: '/app/dashboard/templates',
37
+ file: './edge/routes/cms/dashboard/templates/index.vue',
38
+ children: [
39
+ {
40
+ name: 'cms-dashboard-templates-page',
41
+ path: ':page',
42
+ file: './edge/routes/cms/dashboard/templates/[page].vue',
43
+ },
44
+ ],
45
+ },
46
+ {
47
+ name: 'cms-dashboard-themes-index',
48
+ path: '/app/dashboard/themes',
49
+ file: './edge/routes/cms/dashboard/themes/index.vue',
50
+ },
51
+ {
52
+ name: 'cms-dashboard-themes-theme',
53
+ path: '/app/dashboard/themes/:theme',
54
+ file: './edge/routes/cms/dashboard/themes/[theme].vue',
55
+ },
56
+ ]
package/nuxt.config.ts CHANGED
@@ -1,3 +1,8 @@
1
+ import { createCmsNuxtHooks } from './edge/routes/cms/nuxtHooks'
2
+
3
+ const cmsRouteHooks = {}
4
+ Object.assign(cmsRouteHooks, createCmsNuxtHooks()) // Comment out this line to disable CMS routes.
5
+
1
6
  // https://nuxt.com/docs/api/configuration/nuxt-config
2
7
  export default defineNuxtConfig({
3
8
  ssr: false,
@@ -16,6 +21,12 @@ export default defineNuxtConfig({
16
21
  { rel: 'apple-touch-icon', sizes: '180x180', href: '/favicon/apple-touch-icon.png' },
17
22
  { rel: 'manifest', href: '/favicon/site.webmanifest' },
18
23
  ],
24
+ meta: [
25
+ {
26
+ 'http-equiv': 'Content-Security-Policy',
27
+ 'content': 'font-src \'self\' https://files.edgemarketingdesign.com https://use.typekit.net https://p.typekit.net data:;',
28
+ },
29
+ ],
19
30
  },
20
31
  },
21
32
  modules: ['@vant/nuxt', '@nuxtjs/tailwindcss', '@nuxtjs/color-mode', 'shadcn-nuxt'],
@@ -40,9 +51,11 @@ export default defineNuxtConfig({
40
51
  },
41
52
  components: {
42
53
  dirs: [
43
- { path: '~/components/formSubtypes', global: true, prefix: 'edge-form-subtypes' },
54
+ { path: '~/components' },
55
+
56
+ // Namespaced wrappers — keep them, but lower priority is fine
44
57
  { path: '~/edge/components', global: true, prefix: 'edge' },
45
- '~/components',
58
+ { path: '~/components/formSubtypes', global: true, prefix: 'edge-form-subtypes' },
46
59
  ],
47
60
  },
48
61
  vite: {
@@ -50,11 +63,15 @@ export default defineNuxtConfig({
50
63
  'process.env.DEBUG': false,
51
64
  },
52
65
  server: {
66
+ watch: {
67
+ ignored: ['**/.nuxt/**', '**/.output/**', '**/dist/**', '**/node_modules/**'],
68
+ },
53
69
  hmr: {
54
70
  port: 3000, // Make sure this port matches your Nuxt server port
55
71
  clientPort: 3000, // Ensure this matches your Nuxt server port as well
56
72
  },
57
73
  },
58
74
  },
75
+ hooks: cmsRouteHooks,
59
76
  devtools: { enabled: false },
60
77
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgedev/create-edge-app",
3
- "version": "1.2.29",
3
+ "version": "1.2.30",
4
4
  "description": "Create Edge Starter App",
5
5
  "bin": {
6
6
  "create-edge-app": "./bin/cli.js"
@@ -1,114 +0,0 @@
1
- <script setup>
2
- import { toTypedSchema } from '@vee-validate/zod'
3
- import * as z from 'zod'
4
- import { Loader2 } from 'lucide-vue-next'
5
- const route = useRoute()
6
- // const edgeGlobal = inject('edgeGlobal')
7
-
8
- const edgeFirebase = inject('edgeFirebase')
9
- const isAiBusy = status => status === 'queued' || status === 'running'
10
-
11
- const state = reactive({
12
- filter: '',
13
- newDocs: {
14
- sites: {
15
- name: { bindings: { 'field-type': 'text', 'label': 'Name', 'helper': 'Name' }, cols: '12', value: '' },
16
- },
17
- },
18
- })
19
-
20
- const schemas = {
21
- sites: toTypedSchema(z.object({
22
- name: z.string({
23
- required_error: 'Name is required',
24
- }).min(1, { message: 'Name is required' }),
25
- })),
26
- }
27
-
28
- const collection = computed(() => {
29
- if (route.params.collection) {
30
- return route.params.collection
31
- }
32
- return ''
33
- })
34
- const docId = computed(() => {
35
- if (route.params.docId) {
36
- return route.params.docId
37
- }
38
- return ''
39
- })
40
- definePageMeta({
41
- middleware: 'auth',
42
- })
43
-
44
- onBeforeMount(() => {
45
- // edgeGlobal.showLeftPanel(true)
46
- })
47
- const isAdmin = computed(() => {
48
- return edgeGlobal.isAdminGlobal(edgeFirebase).value
49
- })
50
- const queryField = computed(() => {
51
- if (!isAdmin.value) {
52
- return 'users'
53
- }
54
- return ''
55
- })
56
-
57
- const queryValue = computed(() => {
58
- if (!isAdmin.value) {
59
- return [edgeFirebase?.user?.uid]
60
- }
61
- return ''
62
- })
63
-
64
- const queryOperator = computed(() => {
65
- if (!isAdmin.value) {
66
- return 'array-contains-any'
67
- }
68
- return ''
69
- })
70
- </script>
71
-
72
- <template>
73
- <div
74
- v-if="edgeGlobal.edgeState.organizationDocPath"
75
- >
76
- <edge-dashboard :load-first-if-one="!isAdmin" :filter="state.filter" :query-field="queryField" :query-value="queryValue" :query-operator="queryOperator" collection="sites" class="flex-1 pt-0">
77
- <template #list="slotProps">
78
- <template v-for="item in slotProps.filtered" :key="item.docId">
79
- <edge-shad-button
80
- variant="text"
81
- class="cursor-pointer w-full flex justify-between items-center py-2 gap-3"
82
- :to="isAiBusy(item.aiBootstrapStatus) ? undefined : `/app/dashboard/sites/${item.docId}`"
83
- :disabled="isAiBusy(item.aiBootstrapStatus)"
84
- >
85
- <div>
86
- <Avatar class="cursor-pointer p-0 h-8 w-8 mr-2">
87
- <FilePenLine class="h-5 w-5" />
88
- </Avatar>
89
- </div>
90
- <div class="grow text-left">
91
- <div class="text-lg">
92
- {{ item.name }}
93
- </div>
94
- <div v-if="isAiBusy(item.aiBootstrapStatus)" class="flex items-center gap-2 text-xs text-muted-foreground">
95
- <Loader2 class="h-3 w-3 animate-spin" />
96
- <span>AI is preparing this site</span>
97
- </div>
98
- </div>
99
- <div>
100
- <edge-shad-button
101
- size="icon"
102
- class="bg-slate-600 h-7 w-7"
103
- @click.stop="slotProps.deleteItem(item.docId)"
104
- >
105
- <Trash class="h-5 w-5" />
106
- </edge-shad-button>
107
- </div>
108
- </edge-shad-button>
109
- <Separator class="dark:bg-slate-600" />
110
- </template>
111
- </template>
112
- </edge-dashboard>
113
- </div>
114
- </template>