@edgedev/create-edge-app 1.1.25 → 1.1.26

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 +1298 -0
  50. package/edge/components/cms/themeDefaultMenu.vue +548 -0
  51. package/edge/components/cms/themeEditor.vue +426 -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,548 @@
1
+ <script setup>
2
+ import { computed, reactive, watchEffect } from 'vue'
3
+ import { useVModel } from '@vueuse/core'
4
+ import { File as FileIcon, FileMinus2, FilePen, Folder, FolderMinus, FolderPen, FolderPlus, GripVertical, Plus } from 'lucide-vue-next'
5
+
6
+ const props = defineProps({
7
+ modelValue: {
8
+ type: Object,
9
+ required: true,
10
+ },
11
+ templateOptions: {
12
+ type: Array,
13
+ default: () => [],
14
+ },
15
+ templatePages: {
16
+ type: Object,
17
+ default: () => ({}),
18
+ },
19
+ })
20
+
21
+ const emit = defineEmits(['update:modelValue'])
22
+
23
+ const modelValue = useVModel(props, 'modelValue', emit)
24
+
25
+ const ROOT_MENUS = ['Site Root', 'Not In Menu']
26
+ const dragGroup = { name: 'theme-default-menus', pull: true, put: true }
27
+
28
+ const state = reactive({
29
+ folderDialog: {
30
+ open: false,
31
+ menu: '',
32
+ value: '',
33
+ },
34
+ renameDialog: {
35
+ open: false,
36
+ type: 'page',
37
+ menu: '',
38
+ folder: '',
39
+ index: -1,
40
+ value: '',
41
+ },
42
+ })
43
+
44
+ const ensureMenuStructure = () => {
45
+ if (!modelValue.value || typeof modelValue.value !== 'object' || Array.isArray(modelValue.value))
46
+ modelValue.value = {}
47
+ for (const key of ROOT_MENUS) {
48
+ if (!Array.isArray(modelValue.value[key]))
49
+ modelValue.value[key] = []
50
+ }
51
+ }
52
+
53
+ watchEffect(ensureMenuStructure)
54
+
55
+ const slugify = (value) => {
56
+ return String(value || '')
57
+ .trim()
58
+ .toLowerCase()
59
+ .replace(/[^a-z0-9]+/g, '-')
60
+ .replace(/(^-|-$)+/g, '') || 'page'
61
+ }
62
+
63
+ const uniqueSlug = (value, siblings = [], current = '', options = {}) => {
64
+ const { forceSuffix = false } = options
65
+ const base = slugify(value) || 'page'
66
+ const siblingSet = new Set(siblings.filter(Boolean))
67
+ if (current)
68
+ siblingSet.delete(current)
69
+ if (!forceSuffix) {
70
+ let candidate = base
71
+ let suffix = 1
72
+ while (siblingSet.has(candidate)) {
73
+ candidate = `${base}-${suffix}`
74
+ suffix += 1
75
+ }
76
+ return candidate
77
+ }
78
+ let suffix = 1
79
+ let candidate = `${base}-${suffix}`
80
+ while (siblingSet.has(candidate)) {
81
+ suffix += 1
82
+ candidate = `${base}-${suffix}`
83
+ }
84
+ return candidate
85
+ }
86
+
87
+ const isFolder = entry => entry && typeof entry.item === 'object'
88
+
89
+ const getFolderName = (entry) => {
90
+ if (!isFolder(entry))
91
+ return ''
92
+ return Object.keys(entry.item || {})[0] || ''
93
+ }
94
+
95
+ const getFolderList = (menuName, folderName) => {
96
+ const targetMenu = modelValue.value[menuName] || []
97
+ const folderEntry = targetMenu.find(entry => isFolder(entry) && getFolderName(entry) === folderName)
98
+ if (!folderEntry)
99
+ return null
100
+ return folderEntry.item[folderName]
101
+ }
102
+
103
+ const getParentList = (menuName, folderName = null) => {
104
+ if (!folderName)
105
+ return modelValue.value[menuName]
106
+ return getFolderList(menuName, folderName)
107
+ }
108
+
109
+ const resolveTemplateTitle = (pageId) => {
110
+ return props.templatePages?.[pageId]?.name || 'Untitled Page'
111
+ }
112
+
113
+ const siblingSlugs = (list = [], excludeIndex = -1) => {
114
+ return list
115
+ .map((entry, idx) => (idx === excludeIndex || typeof entry?.item !== 'string') ? null : entry.name)
116
+ .filter(Boolean)
117
+ }
118
+
119
+ const addPageToList = (list, pageId, nameHint) => {
120
+ if (!Array.isArray(list))
121
+ return
122
+ const siblings = siblingSlugs(list)
123
+ const slug = uniqueSlug(nameHint, siblings, '', { forceSuffix: true })
124
+ list.push({
125
+ name: slug,
126
+ item: pageId,
127
+ })
128
+ }
129
+
130
+ const addPageToMenu = (menuName, pageId, folderName = null) => {
131
+ if (!pageId)
132
+ return
133
+ const targetList = getParentList(menuName, folderName)
134
+ if (!targetList)
135
+ return
136
+ addPageToList(targetList, pageId, resolveTemplateTitle(pageId))
137
+ }
138
+
139
+ const removePage = (menuName, index, folderName = null) => {
140
+ const targetList = getParentList(menuName, folderName)
141
+ if (!targetList)
142
+ return
143
+ targetList.splice(index, 1)
144
+ }
145
+
146
+ const folderNamesInMenu = (menuName) => {
147
+ return (modelValue.value[menuName] || [])
148
+ .filter(entry => isFolder(entry))
149
+ .map(entry => getFolderName(entry))
150
+ .filter(Boolean)
151
+ }
152
+
153
+ const uniqueFolderSlug = (value, menuName, current = '') => {
154
+ const names = new Set(folderNamesInMenu(menuName))
155
+ if (current)
156
+ names.delete(current)
157
+ const base = slugify(value) || 'folder'
158
+ let candidate = base
159
+ let suffix = 1
160
+ while (names.has(candidate)) {
161
+ candidate = `${base}-${suffix}`
162
+ suffix += 1
163
+ }
164
+ return candidate
165
+ }
166
+
167
+ const openAddFolderDialog = (menuName) => {
168
+ state.folderDialog.menu = menuName
169
+ state.folderDialog.value = ''
170
+ state.folderDialog.open = true
171
+ }
172
+
173
+ const submitFolderDialog = () => {
174
+ if (!state.folderDialog.menu)
175
+ return
176
+ const value = state.folderDialog.value?.trim()
177
+ if (!value)
178
+ return
179
+ const slug = uniqueFolderSlug(value, state.folderDialog.menu)
180
+ modelValue.value[state.folderDialog.menu].push({
181
+ item: { [slug]: [] },
182
+ })
183
+ state.folderDialog.open = false
184
+ }
185
+
186
+ const canDeleteFolder = (entry) => {
187
+ if (!isFolder(entry))
188
+ return false
189
+ const folderName = getFolderName(entry)
190
+ return !(entry.item?.[folderName]?.length)
191
+ }
192
+
193
+ const deleteFolder = (menuName, index) => {
194
+ const target = modelValue.value[menuName]?.[index]
195
+ if (!target || !canDeleteFolder(target))
196
+ return
197
+ modelValue.value[menuName].splice(index, 1)
198
+ }
199
+
200
+ const openRenameDialogForPage = (menuName, index, folderName = null) => {
201
+ const parentList = getParentList(menuName, folderName)
202
+ if (!parentList?.[index])
203
+ return
204
+ state.renameDialog = {
205
+ open: true,
206
+ type: 'page',
207
+ menu: menuName,
208
+ folder: folderName,
209
+ index,
210
+ value: parentList[index].name || '',
211
+ }
212
+ }
213
+
214
+ const openRenameDialogForFolder = (menuName, folderName, index) => {
215
+ state.renameDialog = {
216
+ open: true,
217
+ type: 'folder',
218
+ menu: menuName,
219
+ folder: folderName,
220
+ index,
221
+ value: folderName,
222
+ }
223
+ }
224
+
225
+ const submitRenameDialog = () => {
226
+ if (!state.renameDialog.open)
227
+ return
228
+ const value = state.renameDialog.value?.trim()
229
+ if (state.renameDialog.type === 'page') {
230
+ const parentList = getParentList(state.renameDialog.menu, state.renameDialog.folder)
231
+ const target = parentList?.[state.renameDialog.index]
232
+ if (!parentList || !target)
233
+ return
234
+ const siblings = siblingSlugs(parentList, state.renameDialog.index)
235
+ target.name = uniqueSlug(value || target.name, siblings, target.name)
236
+ }
237
+ else if (state.renameDialog.type === 'folder') {
238
+ const folderList = modelValue.value[state.renameDialog.menu] || []
239
+ const target = folderList[state.renameDialog.index]
240
+ if (!isFolder(target))
241
+ return
242
+ const currentName = getFolderName(target)
243
+ const slug = uniqueFolderSlug(value || currentName, state.renameDialog.menu, currentName)
244
+ if (slug !== currentName) {
245
+ target.item[slug] = target.item[currentName]
246
+ delete target.item[currentName]
247
+ }
248
+ }
249
+ state.renameDialog.open = false
250
+ }
251
+
252
+ const displayMenuName = menuName => (menuName === 'Site Root' ? 'Site Menu' : menuName)
253
+
254
+ const hasEntries = computed(() => {
255
+ return ROOT_MENUS.some(menu => (modelValue.value[menu] || []).length)
256
+ })
257
+ </script>
258
+
259
+ <template>
260
+ <div class="space-y-6">
261
+ <div
262
+ v-for="menuName in ROOT_MENUS"
263
+ :key="menuName"
264
+ class="rounded-lg border border-border bg-card/30 p-4 space-y-3"
265
+ >
266
+ <div class="flex items-start justify-between gap-3">
267
+ <div>
268
+ <p class="text-sm font-semibold">
269
+ {{ displayMenuName(menuName) }}
270
+ </p>
271
+ <p class="text-xs text-muted-foreground">
272
+ {{ menuName === 'Site Root' ? 'Visible navigation for new sites.' : 'Pages that are created but hidden from navigation.' }}
273
+ </p>
274
+ </div>
275
+ <div class="flex gap-1">
276
+ <DropdownMenu>
277
+ <DropdownMenuTrigger as-child>
278
+ <edge-shad-button
279
+ size="icon"
280
+ variant="ghost"
281
+ class="h-8 w-8"
282
+ :disabled="!templateOptions.length"
283
+ aria-label="Add page"
284
+ >
285
+ <Plus class="w-4 h-4" />
286
+ </edge-shad-button>
287
+ </DropdownMenuTrigger>
288
+ <DropdownMenuContent class="w-64">
289
+ <template v-if="templateOptions.length">
290
+ <DropdownMenuItem
291
+ v-for="option in templateOptions"
292
+ :key="option.value"
293
+ @click="addPageToMenu(menuName, option.value)"
294
+ >
295
+ {{ option.label }}
296
+ </DropdownMenuItem>
297
+ </template>
298
+ <div v-else class="px-3 py-2 text-xs text-muted-foreground">
299
+ All template pages are already assigned.
300
+ </div>
301
+ </DropdownMenuContent>
302
+ </DropdownMenu>
303
+ <edge-shad-button
304
+ size="icon"
305
+ variant="ghost"
306
+ class="h-8 w-8"
307
+ aria-label="Add folder"
308
+ @click="openAddFolderDialog(menuName)"
309
+ >
310
+ <FolderPlus class="w-4 h-4" />
311
+ </edge-shad-button>
312
+ </div>
313
+ </div>
314
+
315
+ <draggable
316
+ :list="modelValue[menuName]"
317
+ handle=".drag-handle"
318
+ item-key="name"
319
+ class="space-y-3"
320
+ :group="dragGroup"
321
+ >
322
+ <template #item="{ element, index }">
323
+ <div v-if="isFolder(element)" class="rounded-md border border-border/60 bg-background/60">
324
+ <div class="flex items-center justify-between gap-2 border-b border-border/60 px-3 py-2">
325
+ <div class="flex items-center gap-2">
326
+ <GripVertical class="w-4 h-4 text-muted-foreground drag-handle" />
327
+ <div>
328
+ <div class="text-sm font-semibold flex items-center gap-1">
329
+ <Folder class="w-4 h-4" />
330
+ {{ getFolderName(element) }}
331
+ </div>
332
+ <div class="text-[11px] text-muted-foreground">
333
+ Folder
334
+ </div>
335
+ </div>
336
+ </div>
337
+ <div class="flex gap-1">
338
+ <edge-shad-button
339
+ variant="ghost"
340
+ size="icon"
341
+ class="h-7 w-7"
342
+ @click="openRenameDialogForFolder(menuName, getFolderName(element), index)"
343
+ >
344
+ <FolderPen class="w-3.5 h-3.5" />
345
+ </edge-shad-button>
346
+ <edge-shad-button
347
+ variant="ghost"
348
+ size="icon"
349
+ class="h-7 w-7"
350
+ :disabled="!canDeleteFolder(element)"
351
+ @click="deleteFolder(menuName, index)"
352
+ >
353
+ <FolderMinus class="w-3.5 h-3.5" />
354
+ </edge-shad-button>
355
+ <DropdownMenu>
356
+ <DropdownMenuTrigger as-child>
357
+ <edge-shad-button
358
+ size="icon"
359
+ class="h-7 w-7"
360
+ variant="ghost"
361
+
362
+ :disabled="!templateOptions.length"
363
+ >
364
+ <Plus class="w-3.5 h-3.5" />
365
+ </edge-shad-button>
366
+ </DropdownMenuTrigger>
367
+ <DropdownMenuContent class="w-64">
368
+ <template v-if="templateOptions.length">
369
+ <DropdownMenuItem
370
+ v-for="option in templateOptions"
371
+ :key="`${getFolderName(element)}-${option.value}`"
372
+ @click="addPageToMenu(menuName, option.value, getFolderName(element))"
373
+ >
374
+ {{ option.label }}
375
+ </DropdownMenuItem>
376
+ </template>
377
+ <div v-else class="px-3 py-2 text-xs text-muted-foreground">
378
+ All template pages are already assigned.
379
+ </div>
380
+ </DropdownMenuContent>
381
+ </DropdownMenu>
382
+ </div>
383
+ </div>
384
+ <div class="space-y-3 border-l border-dashed border-border/80 px-3 py-3 ml-4">
385
+ <div class="flex justify-end" />
386
+ <draggable
387
+ :list="element.item[getFolderName(element)]"
388
+ handle=".drag-handle"
389
+ item-key="name"
390
+ class="space-y-2"
391
+ :group="dragGroup"
392
+ >
393
+ <template #item="{ element: child, index: childIndex }">
394
+ <div class="flex items-center justify-between gap-3 rounded-md border border-border/70 bg-card px-3 py-2">
395
+ <div class="flex items-center gap-2">
396
+ <GripVertical class="w-4 h-4 text-muted-foreground drag-handle" />
397
+ <div>
398
+ <div class="text-sm font-semibold flex items-center gap-1">
399
+ <FileIcon class="w-4 h-4" />
400
+ {{ element.item[getFolderName(element)][childIndex].name }}
401
+ </div>
402
+ <div class="text-[11px] text-muted-foreground">
403
+ Template: {{ resolveTemplateTitle(element.item[getFolderName(element)][childIndex].item) }}
404
+ </div>
405
+ </div>
406
+ </div>
407
+ <div class="flex gap-1">
408
+ <edge-shad-button
409
+ variant="ghost"
410
+ size="icon"
411
+ class="h-7 w-7"
412
+ @click="openRenameDialogForPage(menuName, childIndex, getFolderName(element))"
413
+ >
414
+ <FilePen class="w-3.5 h-3.5" />
415
+ </edge-shad-button>
416
+ <edge-shad-button
417
+ variant="ghost"
418
+ size="icon"
419
+ class="h-7 w-7 text-destructive"
420
+ @click="removePage(menuName, childIndex, getFolderName(element))"
421
+ >
422
+ <FileMinus2 class="w-3.5 h-3.5" />
423
+ </edge-shad-button>
424
+ </div>
425
+ </div>
426
+ </template>
427
+ <template #footer>
428
+ <p
429
+ v-if="!(element.item[getFolderName(element)] || []).length"
430
+ class="text-xs text-muted-foreground italic pl-1"
431
+ >
432
+ No pages in this folder yet.
433
+ </p>
434
+ </template>
435
+ </draggable>
436
+ </div>
437
+ </div>
438
+ <div v-else class="flex items-center justify-between gap-3 rounded-md border border-border/70 bg-card px-3 py-2">
439
+ <div class="flex items-center gap-2">
440
+ <GripVertical class="w-4 h-4 text-muted-foreground drag-handle" />
441
+ <div>
442
+ <div class="text-sm font-semibold flex items-center gap-1">
443
+ <FileIcon class="w-4 h-4" />
444
+ {{ element.name }}
445
+ </div>
446
+ <div class="text-[11px] text-muted-foreground">
447
+ Template: {{ resolveTemplateTitle(element.item) }}
448
+ </div>
449
+ </div>
450
+ </div>
451
+ <div class="flex gap-1">
452
+ <edge-shad-button
453
+ variant="ghost"
454
+ size="icon"
455
+ class="h-7 w-7"
456
+ @click="openRenameDialogForPage(menuName, index)"
457
+ >
458
+ <FilePen class="w-3.5 h-3.5" />
459
+ </edge-shad-button>
460
+ <edge-shad-button
461
+ variant="ghost"
462
+ size="icon"
463
+ class="h-7 w-7 text-destructive"
464
+ @click="removePage(menuName, index)"
465
+ >
466
+ <FileMinus2 class="w-3.5 h-3.5" />
467
+ </edge-shad-button>
468
+ </div>
469
+ </div>
470
+ </template>
471
+ <template #footer>
472
+ <div
473
+ v-if="!(modelValue[menuName] || []).length"
474
+ class="rounded-md border border-dashed border-border/80 px-3 py-2 text-xs text-muted-foreground italic"
475
+ >
476
+ No entries yet. Use the buttons above to add pages or folders.
477
+ </div>
478
+ </template>
479
+ </draggable>
480
+ </div>
481
+
482
+ <div v-if="!hasEntries" class="text-xs text-muted-foreground">
483
+ Start by selecting template pages to include. Each page can only be used once across all menus.
484
+ </div>
485
+ </div>
486
+
487
+ <edge-shad-dialog v-model="state.folderDialog.open">
488
+ <DialogContent class="pt-10">
489
+ <DialogHeader>
490
+ <DialogTitle>Create Folder</DialogTitle>
491
+ </DialogHeader>
492
+ <edge-shad-input
493
+ v-model="state.folderDialog.value"
494
+ name="folderName"
495
+ label="Folder Name"
496
+ placeholder="Enter folder name"
497
+ />
498
+ <DialogFooter class="pt-2">
499
+ <edge-shad-button
500
+ variant="destructive"
501
+ @click="state.folderDialog.open = false"
502
+ >
503
+ Cancel
504
+ </edge-shad-button>
505
+ <edge-shad-button
506
+ type="button"
507
+ class="bg-slate-800 hover:bg-slate-500 text-white"
508
+ :disabled="!state.folderDialog.value?.trim()?.length"
509
+ @click="submitFolderDialog"
510
+ >
511
+ Create
512
+ </edge-shad-button>
513
+ </DialogFooter>
514
+ </DialogContent>
515
+ </edge-shad-dialog>
516
+
517
+ <edge-shad-dialog v-model="state.renameDialog.open">
518
+ <DialogContent class="pt-10">
519
+ <DialogHeader>
520
+ <DialogTitle>
521
+ {{ state.renameDialog.type === 'folder' ? 'Rename Folder' : 'Rename Page Slug' }}
522
+ </DialogTitle>
523
+ </DialogHeader>
524
+ <edge-shad-input
525
+ v-model="state.renameDialog.value"
526
+ name="renameValue"
527
+ label="New Name"
528
+ placeholder="Enter new name"
529
+ />
530
+ <DialogFooter class="pt-2">
531
+ <edge-shad-button
532
+ variant="destructive"
533
+ @click="state.renameDialog.open = false"
534
+ >
535
+ Cancel
536
+ </edge-shad-button>
537
+ <edge-shad-button
538
+ type="button"
539
+ class="bg-slate-800 hover:bg-slate-500 text-white"
540
+ :disabled="!state.renameDialog.value?.trim()?.length"
541
+ @click="submitRenameDialog"
542
+ >
543
+ Save
544
+ </edge-shad-button>
545
+ </DialogFooter>
546
+ </DialogContent>
547
+ </edge-shad-dialog>
548
+ </template>