@edgedev/create-edge-app 1.1.25 → 1.1.27

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 +1475 -0
  50. package/edge/components/cms/themeDefaultMenu.vue +548 -0
  51. package/edge/components/cms/themeEditor.vue +429 -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,671 @@
1
+ <script setup>
2
+ import { CheckCircle2 } from 'lucide-vue-next'
3
+ import { cn } from '@/lib/utils'
4
+ const props = defineProps({
5
+ docId: {
6
+ type: String,
7
+ default: '',
8
+ },
9
+ collection: {
10
+ type: String,
11
+ required: true,
12
+ },
13
+ newDocSchema: {
14
+ type: Object,
15
+ required: true,
16
+ },
17
+ schema: {
18
+ type: Object,
19
+ required: false,
20
+ default: () => ({}),
21
+ },
22
+ showHeader: {
23
+ type: Boolean,
24
+ default: true,
25
+ },
26
+ showFooter: {
27
+ type: Boolean,
28
+ default: true,
29
+ },
30
+ class: {
31
+ type: String,
32
+ default: '',
33
+ },
34
+ stringsToUpperCase: {
35
+ type: Boolean,
36
+ default: false,
37
+ },
38
+ saveRedirectOverride: {
39
+ type: String,
40
+ default: '',
41
+ },
42
+ customDocId: {
43
+ type: String,
44
+ default: '',
45
+ },
46
+ noCloseAfterSave: {
47
+ type: Boolean,
48
+ default: false,
49
+ },
50
+ saveFunctionOverride: {
51
+ type: Function,
52
+ default: null,
53
+ },
54
+ workingDocOverrides: {
55
+ type: Object,
56
+ default: null,
57
+ },
58
+ cardContentClass: {
59
+ type: String,
60
+ default: '',
61
+ },
62
+ titleField: {
63
+ type: String,
64
+ default: 'name',
65
+ },
66
+ })
67
+
68
+ const emit = defineEmits(['unsavedChanges', 'workingDoc', 'error', 'saved'])
69
+
70
+ const newDoc = computed(() => {
71
+ return Object.entries(props.newDocSchema).reduce((newObj, [key, val]) => {
72
+ newObj[key] = val.value
73
+ return newObj
74
+ }, {})
75
+ })
76
+
77
+ const router = useRouter()
78
+ const route = useRoute()
79
+
80
+ const state = reactive({
81
+ workingDoc: {},
82
+ form: false,
83
+ tab: 'forms',
84
+ bypassUnsavedChanges: false,
85
+ bypassRoute: '',
86
+ afterMount: false,
87
+ submitting: false,
88
+ errors: {},
89
+ // When creating a new doc, suppress the very first validation pass that happens right after initial values load
90
+ skipNextValidation: props.docId === 'new',
91
+ overrideClose: false,
92
+ collectionData: {},
93
+ successMessage: '',
94
+ dialog: false,
95
+ })
96
+ const edgeFirebase = inject('edgeFirebase')
97
+ // const edgeGlobal = inject('edgeGlobal')
98
+
99
+ const unsavedChanges = computed(() => {
100
+ if (props.docId === 'new') {
101
+ return false
102
+ }
103
+
104
+ // If the baseline doc is not yet loaded (e.g. on page refresh) avoid flagging unsaved changes
105
+ const baselineDoc = state.collectionData?.[props.docId]
106
+ if (!state.afterMount || !baselineDoc) {
107
+ return false
108
+ }
109
+
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)
113
+ })
114
+
115
+ onBeforeRouteLeave((to, from, next) => {
116
+ state.bypassRoute = to.path
117
+ // console.log('bypassRoute', state.bypassRoute)
118
+ // console.log('unsavedChanges', unsavedChanges.value)
119
+ // console.log('bypassUnsavedChanges', state.bypassUnsavedChanges)
120
+ if (unsavedChanges.value && !state.bypassUnsavedChanges) {
121
+ state.dialog = true
122
+ next(false)
123
+ return
124
+ }
125
+ edgeGlobal.edgeState.changeTracker = {}
126
+ next()
127
+ })
128
+
129
+ onBeforeRouteUpdate((to, from, next) => {
130
+ state.bypassRoute = to.path
131
+ if (unsavedChanges.value && !state.bypassUnsavedChanges) {
132
+ state.dialog = true
133
+ next(false)
134
+ return
135
+ }
136
+ edgeGlobal.edgeState.changeTracker = {}
137
+ next()
138
+ })
139
+
140
+ watch(() => unsavedChanges.value, (newVal) => {
141
+ console.log('test', newVal)
142
+ emit('unsavedChanges', newVal)
143
+ if (newVal) {
144
+ state.successMessage = ''
145
+ }
146
+ })
147
+
148
+ const discardChanges = async () => {
149
+ state.successMessage = ''
150
+ if (props.docId === 'new') {
151
+ state.bypassUnsavedChanges = true
152
+ edgeGlobal.edgeState.changeTracker = {}
153
+ if (props.saveRedirectOverride) {
154
+ router.push(props.saveRedirectOverride)
155
+ }
156
+ else {
157
+ if (props.saveFunctionOverride) {
158
+ emit('unsavedChanges', false)
159
+ props.saveFunctionOverride()
160
+ }
161
+ else {
162
+ router.push(`/app/dashboard/${props.collection}`)
163
+ }
164
+ }
165
+ return
166
+ }
167
+ state.workingDoc = await edgeGlobal.dupObject(state.collectionData[props.docId])
168
+ state.bypassUnsavedChanges = true
169
+ state.dialog = false
170
+ edgeGlobal.edgeState.changeTracker = {}
171
+ if (state.bypassRoute) {
172
+ router.push(state.bypassRoute)
173
+ }
174
+ else {
175
+ if (props.saveRedirectOverride) {
176
+ router.push(props.saveRedirectOverride)
177
+ }
178
+ else {
179
+ if (props.saveFunctionOverride) {
180
+ emit('unsavedChanges', false)
181
+ props.saveFunctionOverride()
182
+ }
183
+ else {
184
+ router.push(`/app/dashboard/${props.collection}`)
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ const capitalizeFirstLetter = (str) => {
191
+ return str.charAt(0).toUpperCase() + str.slice(1)
192
+ }
193
+
194
+ function toTitleCase(str) {
195
+ return str.replace(/\b\w/g, char => char.toUpperCase())
196
+ }
197
+
198
+ // Output: "Hello World From Javascript"
199
+
200
+ const singularize = (word) => {
201
+ if (word.endsWith('ies') && word.length > 4) {
202
+ return `${word.slice(0, -3)}y`
203
+ }
204
+ else if (word.endsWith('es') && word.length > 2) {
205
+ // if the word ends with one of the common "es" patterns, remove "es"
206
+ if (
207
+ word.endsWith('ches')
208
+ || word.endsWith('shes')
209
+ || word.endsWith('xes')
210
+ || word.endsWith('ses')
211
+ || word.endsWith('zes')
212
+ ) {
213
+ return word.slice(0, -2)
214
+ }
215
+ else {
216
+ // otherwise, the plural is likely just the singular plus "s"
217
+ return word.slice(0, -1)
218
+ }
219
+ }
220
+ else if (word.endsWith('s') && word.length > 1) {
221
+ return word.slice(0, -1)
222
+ }
223
+ else {
224
+ return word
225
+ }
226
+ }
227
+
228
+ const title = computed(() => {
229
+ if (props.docId !== 'new') {
230
+ if (!state.collectionData) {
231
+ return ''
232
+ }
233
+ if (!state.collectionData[props.docId]) {
234
+ return ''
235
+ }
236
+ // if (!state.collectionData?.[props.docId]?.name) {
237
+ // return capitalizeFirstLetter(`${state.collectionData[props.docId]}`)
238
+ // }
239
+ return capitalizeFirstLetter(`${state.collectionData[props.docId][props.titleField]}`)
240
+ }
241
+ else {
242
+ return `New ${toTitleCase(singularize(props.collection)).replace('-', ' ')}`
243
+ }
244
+ })
245
+
246
+ const onSubmit = async () => {
247
+ state.successMessage = ''
248
+ const workingDocOverrides = props.workingDocOverrides
249
+ if (workingDocOverrides) {
250
+ Object.keys(workingDocOverrides).forEach((key) => {
251
+ state.workingDoc[key] = workingDocOverrides[key]
252
+ })
253
+ }
254
+ // console.log(state.workingDoc)
255
+ state.submitting = true
256
+ state.bypassUnsavedChanges = true
257
+ Object.keys(state.workingDoc).forEach((key) => {
258
+ const schemaFieldType = props.newDocSchema[key]?.bindings?.['field-type']
259
+ if (typeof state.workingDoc[key] === 'string' && props.stringsToUpperCase) {
260
+ if (key !== 'docId') {
261
+ state.workingDoc[key] = state.workingDoc[key].toUpperCase()
262
+ }
263
+ }
264
+ if (schemaFieldType === 'money') {
265
+ state.workingDoc[key] = Number(parseFloat(state.workingDoc[key]).toFixed(2))
266
+ }
267
+ })
268
+ if (props.customDocId) {
269
+ state.workingDoc.docId = state.workingDoc[props.customDocId]
270
+ }
271
+ // console.log('saving', state.workingDoc)
272
+ const result = await edgeFirebase.storeDoc(`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`, state.workingDoc)
273
+ state.workingDoc.docId = result.meta.docId
274
+ emit('saved', {
275
+ collection: props.collection,
276
+ docId: state.workingDoc.docId,
277
+ data: edgeGlobal.dupObject(state.workingDoc),
278
+ })
279
+ edgeGlobal.edgeState.lastPaginatedDoc = JSON.parse(JSON.stringify(state.workingDoc))
280
+ console.log('save result', result)
281
+ if (state.overrideClose) {
282
+ state.submitting = false
283
+ // state.overrideClose = false
284
+ // state.workingDoc = edgeGlobal.dupObject(state.collectionData[props.docId])
285
+ state.collectionData = edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`]
286
+ emit('unsavedChanges', false)
287
+ // console.log('bypassUnsavedChanges', state.bypassUnsavedChanges)
288
+ edgeGlobal.edgeState.changeTracker = {}
289
+ state.bypassUnsavedChanges = false
290
+ state.successMessage = 'All changes saved. You can close or continue editing.'
291
+ return
292
+ }
293
+ edgeGlobal.edgeState.changeTracker = {}
294
+ state.workingDoc = {}
295
+ if (props.saveRedirectOverride) {
296
+ router.push(props.saveRedirectOverride)
297
+ }
298
+ else {
299
+ if (props.saveFunctionOverride) {
300
+ emit('unsavedChanges', false)
301
+ props.saveFunctionOverride()
302
+ }
303
+ else {
304
+ router.push(`/app/dashboard/${props.collection}`)
305
+ }
306
+ }
307
+ state.submitting = false
308
+ }
309
+
310
+ const onCancel = () => {
311
+ state.successMessage = ''
312
+ if (props.saveRedirectOverride) {
313
+ router.push(props.saveRedirectOverride)
314
+ }
315
+ else {
316
+ if (props.saveFunctionOverride) {
317
+ emit('unsavedChanges', false)
318
+ props.saveFunctionOverride()
319
+ }
320
+ else {
321
+ router.push(`/app/dashboard/${props.collection}`)
322
+ }
323
+ }
324
+ }
325
+
326
+ const initData = (newVal) => {
327
+ if (props.docId !== 'new') {
328
+ if (edgeGlobal.objHas(newVal, props.docId) === false) {
329
+ return
330
+ }
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
+ })
337
+ state.afterMount = true
338
+ }
339
+ else {
340
+ if (!state.afterMount) {
341
+ state.workingDoc = edgeGlobal.dupObject(newDoc.value)
342
+ }
343
+ state.afterMount = true
344
+ }
345
+ }
346
+
347
+ onBeforeMount(async () => {
348
+ state.bypassUnsavedChanges = false
349
+ edgeGlobal.edgeState.changeTracker = {}
350
+ for (const field of Object.keys(props.newDocSchema)) {
351
+ if (props.newDocSchema[field].type === 'collection') {
352
+ await edgeFirebase.startSnapshot(`${edgeGlobal.edgeState.organizationDocPath}/${field}`)
353
+ }
354
+ }
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}`]
368
+ }
369
+ if (props.noCloseAfterSave) {
370
+ state.overrideClose = true
371
+ }
372
+ })
373
+
374
+ watch(() => state.collectionData, (newVal) => {
375
+ initData(newVal)
376
+ })
377
+ onActivated(() => {
378
+ // console.log('activated')
379
+ state.bypassUnsavedChanges = false
380
+ state.bypassRoute = ''
381
+ // console.log('bypass', state.bypassUnsavedChanges)
382
+ state.afterMount = false
383
+ if (props.docId !== 'new') {
384
+ if (edgeGlobal.objHas(state.collectionData, props.docId) === false) {
385
+ return
386
+ }
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
+ })
393
+
394
+ // console.log('state.workingDoc', state.workingDoc)
395
+ }
396
+ else {
397
+ state.workingDoc = edgeGlobal.dupObject(newDoc.value)
398
+ Object.entries(route.query).forEach(([key, value]) => {
399
+ // Check if the key exists in state.workingDoc, and if so, set the value
400
+ if (key in state.workingDoc) {
401
+ state.workingDoc[key] = value
402
+ }
403
+ })
404
+ // console.log('state.workingDoc', state.workingDoc)
405
+ }
406
+ //
407
+ nextTick(() => {
408
+ state.afterMount = true
409
+ })
410
+ })
411
+
412
+ const WIDTHS = {
413
+ 1: 'md:w-1/12',
414
+ 2: 'md:w-2/12',
415
+ 3: 'md:w-3/12',
416
+ 4: 'md:w-4/12',
417
+ 5: 'md:w-5/12',
418
+ 6: 'md:w-6/12',
419
+ 7: 'md:w-7/12',
420
+ 8: 'md:w-8/12',
421
+ 9: 'md:w-9/12',
422
+ 10: 'md:w-10/12',
423
+ 11: 'md:w-11/12',
424
+ 12: 'w-full',
425
+ }
426
+
427
+ const numColsToTailwind = cols => WIDTHS[cols] || 'md:w-full'
428
+
429
+ const formRef = ref(null)
430
+
431
+ const onKeydown = (event) => {
432
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {
433
+ event.preventDefault()
434
+ if (formRef.value) {
435
+ if (props.noCloseAfterSave) {
436
+ state.overrideClose = true
437
+ }
438
+ formRef.value.handleSubmit(onSubmit)()
439
+ }
440
+ }
441
+ }
442
+
443
+ onMounted(() => {
444
+ if (props.noCloseAfterSave) {
445
+ window.addEventListener('keydown', onKeydown)
446
+ }
447
+ })
448
+ onUnmounted(() => {
449
+ if (props.noCloseAfterSave) {
450
+ window.removeEventListener('keydown', onKeydown)
451
+ }
452
+ })
453
+
454
+ const triggerSubmit = async (insertedValues = {}) => {
455
+ if (formRef.value) {
456
+ Object.keys(insertedValues).forEach((key) => {
457
+ if (insertedValues[key]) {
458
+ state.workingDoc[key] = insertedValues[key]
459
+ }
460
+ })
461
+ await nextTick()
462
+ await formRef.value.setValues(state.workingDoc, true)
463
+ await formRef.value.validate()
464
+ console.log(formRef.value?.errors)
465
+ await nextTick()
466
+ await formRef.value.handleSubmit(onSubmit)()
467
+ await nextTick()
468
+ console.log(formRef.value?.errors)
469
+ state.errors = formRef.value?.errors
470
+ }
471
+ }
472
+
473
+ watch(() => state.workingDoc, async () => {
474
+ emit('workingDoc', state.workingDoc)
475
+ // Do nothing until the component signals it's ready
476
+ if (state.afterMount === false)
477
+ return
478
+
479
+ if (!formRef.value)
480
+ return
481
+
482
+ // If this is the first change on a brand-new doc, set values WITHOUT validation and exit.
483
+ if (state.skipNextValidation && props.docId === 'new') {
484
+ await formRef.value.setValues(state.workingDoc, false) // no validate on initial fill
485
+ state.skipNextValidation = false
486
+ return
487
+ }
488
+
489
+ // Normal behavior thereafter: update values and validate
490
+ await formRef.value.setValues(state.workingDoc, true)
491
+ await formRef.value.validate()
492
+ await nextTick()
493
+ state.errors = formRef.value?.errors
494
+ emit('unsavedChanges', unsavedChanges.value)
495
+ // console.log('formRef.value.errors', state.errors)
496
+ }, { deep: true, immediate: false })
497
+
498
+ const onError = async () => {
499
+ // console.log('form error', formRef.value?.errors)
500
+ if (!formRef.value)
501
+ return
502
+ await formRef.value.setValues(state.workingDoc, false) // sync without triggering per-field validate
503
+ await formRef.value.validate() // run full form validation once
504
+ state.errors = formRef.value?.errors // reflect in UI
505
+ emit('error', state.errors)
506
+ }
507
+ </script>
508
+
509
+ <template>
510
+ <Card v-if="state.afterMount" :class="cn('bg-muted/50 w-full flex-1 border-none shadow-none pt-2 px-2 m-auto flex-col', props.class)">
511
+ <edge-shad-form
512
+ ref="formRef"
513
+ v-model="state.form"
514
+ :schema="props.schema"
515
+ :initial-values="state.workingDoc"
516
+ class="flex flex-col flex-1"
517
+ @submit="onSubmit"
518
+ @error="onError"
519
+ >
520
+ <slot name="header" :on-submit="triggerSubmit" :on-cancel="onCancel" :submitting="state.submitting" :unsaved-changes="unsavedChanges" :title="title" :working-doc="state.workingDoc" :errors="state.errors">
521
+ <edge-menu v-if="props.showHeader" class="py-4 bg-secondary text-foreground rounded-none sticky top-0">
522
+ <template #start>
523
+ <slot name="header-start" :unsaved-changes="unsavedChanges" :title="title" :errors="state.errors" :working-doc="state.workingDoc">
524
+ <FilePenLine class="mr-2" />
525
+ {{ title }}
526
+ </slot>
527
+ </template>
528
+ <template #center>
529
+ <slot name="header-center" :unsaved-changes="unsavedChanges" :title="title" :working-doc="state.workingDoc" />
530
+ </template>
531
+ <template #end>
532
+ <slot name="header-end" :on-submit="triggerSubmit" :unsaved-changes="unsavedChanges" :on-cancel="onCancel" :errors="state.errors" :submitting="state.submitting" :title="title" :working-doc="state.workingDoc">
533
+ <edge-shad-button
534
+ v-if="!unsavedChanges"
535
+ class="bg-red-700 uppercase h-8 hover:bg-red-400 w-20"
536
+ @click="onCancel"
537
+ >
538
+ Close
539
+ </edge-shad-button>
540
+ <edge-shad-button
541
+ v-else
542
+ class="bg-red-700 uppercase h-8 hover:bg-red-400 w-20"
543
+ @click="onCancel"
544
+ >
545
+ Cancel
546
+ </edge-shad-button>
547
+ <edge-shad-button
548
+ type="submit"
549
+ class="bg-primary uppercase h-8 hover:bg-slate-400 px-8"
550
+ :disabled="state.submitting"
551
+ >
552
+ <Loader2 v-if="state.submitting" class="w-4 h-4 mr-2 animate-spin" />
553
+ <span v-if="state.submitting">Saving...</span>
554
+ <span v-else>Save</span>
555
+ </edge-shad-button>
556
+ </slot>
557
+ </template>
558
+ </edge-menu>
559
+ </slot>
560
+ <CardContent :class="cn('flex-1 flex flex-col px-4', props.cardContentClass)">
561
+ <Alert
562
+ v-if="state.successMessage"
563
+ class="mb-4 border border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-800 dark:bg-emerald-900/30 mt-2"
564
+ >
565
+ <CheckCircle2 class="h-5 w-5" />
566
+ <AlertTitle>
567
+ Changes saved
568
+ </AlertTitle>
569
+ <AlertDescription>
570
+ {{ state.successMessage }}
571
+ </AlertDescription>
572
+ </Alert>
573
+ <slot name="main" :title="title" :on-cancel="onCancel" :submitting="state.submitting" :unsaved-changes="unsavedChanges" :on-submit="triggerSubmit" :working-doc="state.workingDoc">
574
+ <div class="flex flex-wrap justify-between">
575
+ <div v-for="(field, name, index) in props.newDocSchema" :key="index" class="w-full" :class="numColsToTailwind(field.cols)">
576
+ <div v-if="field.bindings['field-type'] === 'date'" class="p-3 items-center">
577
+ <edge-shad-datepicker
578
+ v-model="state.workingDoc[name]"
579
+ :label="field.bindings.label"
580
+ :name="name"
581
+ :bindings="field.bindings"
582
+ :errors="state.errors?.[name]"
583
+ :pass-through-props="state.workingDoc"
584
+ />
585
+ </div>
586
+ <div v-else-if="field.bindings['field-type'] !== 'collection'" :class="field.bindings['field-type'] === 'textarea' ? 'mb-10' : ''" class="p-3 items-center">
587
+ <edge-g-input
588
+ v-model="state.workingDoc[name]"
589
+ :name="name"
590
+ :disable-tracking="true"
591
+ :parent-tracker-id="`${props.collection}-${props.docId}`"
592
+ v-bind="field.bindings"
593
+ :bindings="field.bindings"
594
+ :errors="state.errors?.[name]"
595
+ :pass-through-props="state.workingDoc"
596
+ />
597
+ </div>
598
+ <div v-else class="p-3 items-center">
599
+ <edge-g-input
600
+ v-model="state.workingDoc[name]"
601
+ :disable-tracking="true"
602
+ field-type="collection"
603
+ :collection-path="`${edgeGlobal.edgeState.organizationDocPath}/${field.bindings['collection-path']}`"
604
+ :label="field.bindings.label"
605
+ :name="name"
606
+ :bindings="field.bindings"
607
+ :parent-tracker-id="`${props.collection}-${props.docId}`"
608
+ :errors="state.errors?.[name]"
609
+ :pass-through-props="state.workingDoc"
610
+ />
611
+ </div>
612
+ </div>
613
+ </div>
614
+ </slot>
615
+ <edge-shad-dialog v-model="state.dialog">
616
+ <DialogContent>
617
+ <DialogHeader>
618
+ <DialogTitle>
619
+ Unsaved Changes!
620
+ </DialogTitle>
621
+ <DialogDescription>
622
+ <h4>"{{ title }}" has unsaved changes.</h4>
623
+ <p>Are you sure you want to discard them?</p>
624
+ </DialogDescription>
625
+ </DialogHeader>
626
+ <DialogFooter class="pt-2 flex justify-between">
627
+ <edge-shad-button class="text-white bg-slate-800 hover:bg-slate-400" @click="state.dialog = false">
628
+ Cancel
629
+ </edge-shad-button>
630
+ <edge-shad-button variant="destructive" class="text-white w-full" @click="discardChanges()">
631
+ Discard
632
+ </edge-shad-button>
633
+ </DialogFooter>
634
+ </DialogContent>
635
+ </edge-shad-dialog>
636
+ </CardContent>
637
+ <CardFooter v-if="showFooter" class="flex gap-1">
638
+ <slot name="footer" :on-submit="triggerSubmit" :unsaved-changes="unsavedChanges" :title="title" :submitting="state.submitting" :working-doc="state.workingDoc">
639
+ <edge-shad-button
640
+ v-if="!unsavedChanges"
641
+ class="bg-red-700 uppercase h-8 hover:bg-red-200 w-20"
642
+ @click="onCancel"
643
+ >
644
+ Close
645
+ </edge-shad-button>
646
+ <edge-shad-button
647
+ v-else
648
+ class="bg-red-700 uppercase h-8 hover:bg-red-200 w-20"
649
+ @click="onCancel"
650
+ >
651
+ Cancel
652
+ </edge-shad-button>
653
+
654
+ <edge-shad-button
655
+ type="submit"
656
+ class="bg-primary uppercase h-8 hover:bg-slate-400 px-8"
657
+ :disabled="state.submitting"
658
+ >
659
+ <Loader2 v-if="state.submitting" class="w-4 h-4 mr-2 animate-spin" />
660
+ <span v-if="state.submitting">Saving...</span>
661
+ <span v-else>Save</span>
662
+ </edge-shad-button>
663
+ </slot>
664
+ </CardFooter>
665
+ </edge-shad-form>
666
+ </Card>
667
+ </template>
668
+
669
+ <style lang="scss" scoped>
670
+
671
+ </style>