@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
@@ -0,0 +1,623 @@
1
+ <script setup lang="js">
2
+ import { CircleAlert } from 'lucide-vue-next'
3
+
4
+ const props = defineProps({
5
+ settings: {
6
+ type: Object,
7
+ required: true,
8
+ },
9
+ themeOptions: {
10
+ type: Array,
11
+ default: () => [],
12
+ },
13
+ userOptions: {
14
+ type: Array,
15
+ default: () => [],
16
+ },
17
+ hasUsers: {
18
+ type: Boolean,
19
+ default: false,
20
+ },
21
+ showUsers: {
22
+ type: Boolean,
23
+ default: false,
24
+ },
25
+ showThemeFields: {
26
+ type: Boolean,
27
+ default: true,
28
+ },
29
+ isAdmin: {
30
+ type: Boolean,
31
+ default: false,
32
+ },
33
+ enableMediaPicker: {
34
+ type: Boolean,
35
+ default: false,
36
+ },
37
+ siteId: {
38
+ type: String,
39
+ default: '',
40
+ },
41
+ domainError: {
42
+ type: String,
43
+ default: '',
44
+ },
45
+ })
46
+
47
+ const edgeFirebase = inject('edgeFirebase')
48
+
49
+ const state = reactive({
50
+ logoPickerOpen: false,
51
+ logoLightPickerOpen: false,
52
+ brandLogoDarkPickerOpen: false,
53
+ brandLogoLightPickerOpen: false,
54
+ faviconPickerOpen: false,
55
+ })
56
+
57
+ const themeOptionsMap = computed(() => {
58
+ const map = new Map()
59
+ for (const option of props.themeOptions) {
60
+ if (option?.value)
61
+ map.set(option.value, option)
62
+ }
63
+ return map
64
+ })
65
+
66
+ const isLightName = (value) => {
67
+ if (!value)
68
+ return false
69
+ return String(value).toLowerCase().includes('light')
70
+ }
71
+
72
+ const previewBackgroundClass = value => (isLightName(value) ? 'bg-neutral-900/90' : 'bg-neutral-100')
73
+
74
+ const themeItemsForAllowed = (allowed, current) => {
75
+ const base = props.themeOptions || []
76
+ const allowedList = Array.isArray(allowed) ? allowed.filter(Boolean) : []
77
+ if (allowedList.length) {
78
+ const allowedSet = new Set(allowedList)
79
+ const filtered = base.filter(option => allowedSet.has(option.value))
80
+ if (current && !allowedSet.has(current)) {
81
+ const currentOption = themeOptionsMap.value.get(current)
82
+ if (currentOption)
83
+ filtered.push(currentOption)
84
+ }
85
+ return filtered
86
+ }
87
+
88
+ if (current) {
89
+ const currentOption = themeOptionsMap.value.get(current)
90
+ return currentOption ? [currentOption] : []
91
+ }
92
+
93
+ return []
94
+ }
95
+
96
+ const menuPositionOptions = [
97
+ { value: 'left', label: 'Left' },
98
+ { value: 'center', label: 'Center' },
99
+ { value: 'right', label: 'Right' },
100
+ ]
101
+
102
+ const domainError = computed(() => String(props.domainError || '').trim())
103
+ const serverPagesProject = ref('')
104
+ const pagesProject = computed(() => String(serverPagesProject.value || '').trim())
105
+ const pagesDomain = computed(() => (pagesProject.value ? `${pagesProject.value}.pages.dev` : '(CLOUDFLARE_PAGES_PROJECT).pages.dev'))
106
+
107
+ onMounted(async () => {
108
+ if (!edgeFirebase?.runFunction)
109
+ return
110
+ try {
111
+ const response = await edgeFirebase.runFunction('cms-getCloudflarePagesProject', {})
112
+ serverPagesProject.value = String(response?.data?.project || '').trim()
113
+ }
114
+ catch {
115
+ serverPagesProject.value = ''
116
+ }
117
+ })
118
+ </script>
119
+
120
+ <template>
121
+ <Tabs class="w-full" default-value="general">
122
+ <TabsList class="w-full gap-2 bg-muted/40 p-1 rounded-lg">
123
+ <TabsTrigger value="general" class="text-sm uppercase font-medium">
124
+ General
125
+ </TabsTrigger>
126
+ <TabsTrigger value="appearance" class="text-sm uppercase font-medium">
127
+ Appearance
128
+ </TabsTrigger>
129
+ <TabsTrigger value="branding" class="text-sm uppercase font-medium">
130
+ Branding
131
+ </TabsTrigger>
132
+ <TabsTrigger value="seo" class="text-sm uppercase font-medium">
133
+ SEO
134
+ </TabsTrigger>
135
+ <TabsTrigger value="tracking" class="text-sm uppercase font-medium">
136
+ Tracking Pixels
137
+ </TabsTrigger>
138
+ <TabsTrigger value="social" class="text-sm uppercase font-medium">
139
+ Social Media
140
+ </TabsTrigger>
141
+ </TabsList>
142
+ <TabsContent value="general" class="pt-4 space-y-4">
143
+ <edge-shad-input
144
+ v-model="props.settings.name"
145
+ name="name"
146
+ label="Name"
147
+ placeholder="Enter name"
148
+ class="w-full"
149
+ />
150
+ <edge-shad-tags
151
+ v-model="props.settings.domains"
152
+ name="domains"
153
+ label="Domains"
154
+ placeholder="Add or remove domains"
155
+ class="w-full"
156
+ />
157
+ <Alert v-if="domainError" variant="destructive">
158
+ <CircleAlert class="h-4 w-4" />
159
+ <AlertTitle>Domain error</AlertTitle>
160
+ <AlertDescription class="text-sm">
161
+ {{ domainError }}
162
+ </AlertDescription>
163
+ </Alert>
164
+ <div class="rounded-lg border border-border/60 bg-muted/40 p-4 space-y-3">
165
+ <div class="flex items-center justify-between">
166
+ <div class="text-sm font-semibold text-foreground">
167
+ Domain DNS records
168
+ </div>
169
+ <div class="text-xs text-muted-foreground">
170
+ Target: <span class="font-mono">{{ pagesDomain }}</span>
171
+ </div>
172
+ </div>
173
+ <p class="text-sm text-muted-foreground">
174
+ Add these records at your DNS provider.
175
+ </p>
176
+ <div class="space-y-2 text-sm">
177
+ <div class="grid grid-cols-[70px_1fr] gap-3">
178
+ <div class="text-muted-foreground">CNAME</div>
179
+ <div class="font-mono">www → {{ pagesDomain }}</div>
180
+ </div>
181
+ <div class="grid grid-cols-[70px_1fr] gap-3">
182
+ <div class="text-muted-foreground">CNAME</div>
183
+ <div class="font-mono">@ → {{ pagesDomain }}</div>
184
+ </div>
185
+ </div>
186
+ <p class="text-xs text-muted-foreground">
187
+ Then forward the root/apex (TLD) domain to <span class="font-mono">www</span> with a 301 redirect.
188
+ </p>
189
+ </div>
190
+ <edge-shad-input
191
+ v-model="props.settings.contactEmail"
192
+ name="contactEmail"
193
+ label="Contact Email"
194
+ placeholder="name@example.com"
195
+ class="w-full"
196
+ />
197
+ <edge-shad-input
198
+ v-model="props.settings.contactPhone"
199
+ name="contactPhone"
200
+ type="tel"
201
+ label="Contact Phone"
202
+ placeholder="(555) 555-5555"
203
+ :mask-options="{ mask: '(###) ###-####' }"
204
+ class="w-full"
205
+ />
206
+ <edge-shad-select-tags
207
+ v-if="props.showUsers && props.hasUsers && props.isAdmin"
208
+ v-model="props.settings.users"
209
+ :items="props.userOptions"
210
+ name="users"
211
+ label="Users"
212
+ item-title="label"
213
+ item-value="value"
214
+ placeholder="Select users"
215
+ class="w-full"
216
+ :multiple="true"
217
+ />
218
+ <p v-else-if="props.showUsers" class="text-sm text-muted-foreground">
219
+ No organization users available for this site.
220
+ </p>
221
+ </TabsContent>
222
+ <TabsContent value="appearance" class="pt-4 space-y-4">
223
+ <edge-shad-select-tags
224
+ v-if="props.showThemeFields && props.isAdmin"
225
+ :model-value="Array.isArray(props.settings.allowedThemes) ? props.settings.allowedThemes : []"
226
+ name="allowedThemes"
227
+ label="Allowed Themes"
228
+ placeholder="Select allowed themes"
229
+ class="w-full"
230
+ :items="props.themeOptions"
231
+ item-title="label"
232
+ item-value="value"
233
+ @update:model-value="(value) => {
234
+ const normalized = Array.isArray(value) ? value : []
235
+ props.settings.allowedThemes = normalized
236
+ if (normalized.length && !normalized.includes(props.settings.theme)) {
237
+ props.settings.theme = normalized[0] || ''
238
+ }
239
+ }"
240
+ />
241
+ <edge-shad-select
242
+ v-if="props.showThemeFields"
243
+ :model-value="props.settings.theme || ''"
244
+ name="theme"
245
+ label="Theme"
246
+ placeholder="Select a theme"
247
+ class="w-full"
248
+ :items="themeItemsForAllowed(props.settings.allowedThemes, props.settings.theme)"
249
+ item-title="label"
250
+ item-value="value"
251
+ @update:model-value="value => (props.settings.theme = value || '')"
252
+ />
253
+ <edge-shad-select
254
+ :model-value="props.settings.menuPosition || ''"
255
+ name="menuPosition"
256
+ label="Menu Position"
257
+ placeholder="Select menu position"
258
+ class="w-full"
259
+ :items="menuPositionOptions"
260
+ item-title="label"
261
+ item-value="value"
262
+ @update:model-value="value => (props.settings.menuPosition = value || '')"
263
+ />
264
+ </TabsContent>
265
+ <TabsContent value="branding" class="pt-4 space-y-4">
266
+ <div v-if="props.enableMediaPicker && props.siteId" class="space-y-2">
267
+ <label class="text-sm font-medium text-foreground flex items-center justify-between">
268
+ Dark logo
269
+ <edge-shad-button
270
+ type="button"
271
+ variant="link"
272
+ class="px-0 h-auto text-sm"
273
+ @click="state.logoPickerOpen = !state.logoPickerOpen"
274
+ >
275
+ {{ state.logoPickerOpen ? 'Hide picker' : 'Select logo' }}
276
+ </edge-shad-button>
277
+ </label>
278
+ <div class="flex items-center gap-4">
279
+ <div v-if="props.settings.logo" class="flex items-center gap-3">
280
+ <img
281
+ :src="props.settings.logo"
282
+ alt="Logo preview"
283
+ :class="['max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.logo)]"
284
+ >
285
+ <edge-shad-button
286
+ type="button"
287
+ variant="ghost"
288
+ class="h-8"
289
+ @click="props.settings.logo = ''"
290
+ >
291
+ Remove
292
+ </edge-shad-button>
293
+ </div>
294
+ <span v-else class="text-sm text-muted-foreground italic">No logo selected</span>
295
+ </div>
296
+ <div v-if="state.logoPickerOpen" class="mt-2 border border-dashed rounded-lg p-2">
297
+ <edge-cms-media-manager
298
+ :site="props.siteId"
299
+ :select-mode="true"
300
+ :default-tags="['Logos']"
301
+ @select="(url) => {
302
+ props.settings.logo = url
303
+ state.logoPickerOpen = false
304
+ }"
305
+ />
306
+ </div>
307
+ </div>
308
+ <edge-shad-input
309
+ v-else
310
+ v-model="props.settings.logo"
311
+ name="logo"
312
+ label="Dark logo URL"
313
+ placeholder="https://..."
314
+ class="w-full"
315
+ />
316
+ <div v-if="props.enableMediaPicker && props.siteId" class="space-y-2">
317
+ <label class="text-sm font-medium text-foreground flex items-center justify-between">
318
+ Light logo
319
+ <edge-shad-button
320
+ type="button"
321
+ variant="link"
322
+ class="px-0 h-auto text-sm"
323
+ @click="state.logoLightPickerOpen = !state.logoLightPickerOpen"
324
+ >
325
+ {{ state.logoLightPickerOpen ? 'Hide picker' : 'Select logo' }}
326
+ </edge-shad-button>
327
+ </label>
328
+ <div class="flex items-center gap-4">
329
+ <div v-if="props.settings.logoLight" class="flex items-center gap-3">
330
+ <img
331
+ :src="props.settings.logoLight"
332
+ alt="Light logo preview"
333
+ :class="['max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.logoLight)]"
334
+ >
335
+ <edge-shad-button
336
+ type="button"
337
+ variant="ghost"
338
+ class="h-8"
339
+ @click="props.settings.logoLight = ''"
340
+ >
341
+ Remove
342
+ </edge-shad-button>
343
+ </div>
344
+ <span v-else class="text-sm text-muted-foreground italic">No light logo selected</span>
345
+ </div>
346
+ <div v-if="state.logoLightPickerOpen" class="mt-2 border border-dashed rounded-lg p-2">
347
+ <edge-cms-media-manager
348
+ :site="props.siteId"
349
+ :select-mode="true"
350
+ :default-tags="['Logos']"
351
+ @select="(url) => {
352
+ props.settings.logoLight = url
353
+ state.logoLightPickerOpen = false
354
+ }"
355
+ />
356
+ </div>
357
+ </div>
358
+ <edge-shad-input
359
+ v-else
360
+ v-model="props.settings.logoLight"
361
+ name="logoLight"
362
+ label="Light logo URL"
363
+ placeholder="https://..."
364
+ class="w-full"
365
+ />
366
+ <div v-if="props.isAdmin" class="space-y-4 border border-dashed rounded-lg p-4">
367
+ <div class="text-sm font-semibold text-foreground">
368
+ Umbrella Brand
369
+ </div>
370
+ <div v-if="props.enableMediaPicker && props.siteId" class="space-y-2">
371
+ <label class="text-sm font-medium text-foreground flex items-center justify-between">
372
+ Dark brand logo
373
+ <edge-shad-button
374
+ type="button"
375
+ variant="link"
376
+ class="px-0 h-auto text-sm"
377
+ @click="state.brandLogoDarkPickerOpen = !state.brandLogoDarkPickerOpen"
378
+ >
379
+ {{ state.brandLogoDarkPickerOpen ? 'Hide picker' : 'Select logo' }}
380
+ </edge-shad-button>
381
+ </label>
382
+ <div class="flex items-center gap-4">
383
+ <div v-if="props.settings.brandLogoDark" class="flex items-center gap-3">
384
+ <img
385
+ :src="props.settings.brandLogoDark"
386
+ alt="Brand dark logo preview"
387
+ :class="['max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.brandLogoDark)]"
388
+ >
389
+ <edge-shad-button
390
+ type="button"
391
+ variant="ghost"
392
+ class="h-8"
393
+ @click="props.settings.brandLogoDark = ''"
394
+ >
395
+ Remove
396
+ </edge-shad-button>
397
+ </div>
398
+ <span v-else class="text-sm text-muted-foreground italic">No brand dark logo selected</span>
399
+ </div>
400
+ <div v-if="state.brandLogoDarkPickerOpen" class="mt-2 border border-dashed rounded-lg p-2">
401
+ <edge-cms-media-manager
402
+ :site="props.siteId"
403
+ :select-mode="true"
404
+ :default-tags="['Logos']"
405
+ @select="(url) => {
406
+ props.settings.brandLogoDark = url
407
+ state.brandLogoDarkPickerOpen = false
408
+ }"
409
+ />
410
+ </div>
411
+ </div>
412
+ <edge-shad-input
413
+ v-else
414
+ v-model="props.settings.brandLogoDark"
415
+ name="brandLogoDark"
416
+ label="Dark brand logo URL"
417
+ placeholder="https://..."
418
+ class="w-full"
419
+ />
420
+ <div v-if="props.enableMediaPicker && props.siteId" class="space-y-2">
421
+ <label class="text-sm font-medium text-foreground flex items-center justify-between">
422
+ Light brand logo
423
+ <edge-shad-button
424
+ type="button"
425
+ variant="link"
426
+ class="px-0 h-auto text-sm"
427
+ @click="state.brandLogoLightPickerOpen = !state.brandLogoLightPickerOpen"
428
+ >
429
+ {{ state.brandLogoLightPickerOpen ? 'Hide picker' : 'Select logo' }}
430
+ </edge-shad-button>
431
+ </label>
432
+ <div class="flex items-center gap-4">
433
+ <div v-if="props.settings.brandLogoLight" class="flex items-center gap-3">
434
+ <img
435
+ :src="props.settings.brandLogoLight"
436
+ alt="Brand light logo preview"
437
+ :class="['max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.brandLogoLight)]"
438
+ >
439
+ <edge-shad-button
440
+ type="button"
441
+ variant="ghost"
442
+ class="h-8"
443
+ @click="props.settings.brandLogoLight = ''"
444
+ >
445
+ Remove
446
+ </edge-shad-button>
447
+ </div>
448
+ <span v-else class="text-sm text-muted-foreground italic">No brand light logo selected</span>
449
+ </div>
450
+ <div v-if="state.brandLogoLightPickerOpen" class="mt-2 border border-dashed rounded-lg p-2">
451
+ <edge-cms-media-manager
452
+ :site="props.siteId"
453
+ :select-mode="true"
454
+ :default-tags="['Logos']"
455
+ @select="(url) => {
456
+ props.settings.brandLogoLight = url
457
+ state.brandLogoLightPickerOpen = false
458
+ }"
459
+ />
460
+ </div>
461
+ </div>
462
+ <edge-shad-input
463
+ v-else
464
+ v-model="props.settings.brandLogoLight"
465
+ name="brandLogoLight"
466
+ label="Light brand logo URL"
467
+ placeholder="https://..."
468
+ class="w-full"
469
+ />
470
+ </div>
471
+ <div v-if="props.enableMediaPicker && props.siteId" class="space-y-2">
472
+ <label class="text-sm font-medium text-foreground flex items-center justify-between">
473
+ Favicon
474
+ <edge-shad-button
475
+ type="button"
476
+ variant="link"
477
+ class="px-0 h-auto text-sm"
478
+ @click="state.faviconPickerOpen = !state.faviconPickerOpen"
479
+ >
480
+ {{ state.faviconPickerOpen ? 'Hide picker' : 'Select favicon' }}
481
+ </edge-shad-button>
482
+ </label>
483
+ <div class="flex items-center gap-4">
484
+ <div v-if="props.settings.favicon" class="flex items-center gap-3">
485
+ <img
486
+ :src="props.settings.favicon"
487
+ alt="Favicon preview"
488
+ :class="['max-h-12 max-w-12 h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.favicon)]"
489
+ >
490
+ <edge-shad-button
491
+ type="button"
492
+ variant="ghost"
493
+ class="h-8"
494
+ @click="props.settings.favicon = ''"
495
+ >
496
+ Remove
497
+ </edge-shad-button>
498
+ </div>
499
+ <span v-else class="text-sm text-muted-foreground italic">No favicon selected</span>
500
+ </div>
501
+ <div v-if="state.faviconPickerOpen" class="mt-2 border border-dashed rounded-lg p-2">
502
+ <edge-cms-media-manager
503
+ :site="props.siteId"
504
+ :select-mode="true"
505
+ :default-tags="['Logos']"
506
+ @select="(url) => {
507
+ props.settings.favicon = url
508
+ state.faviconPickerOpen = false
509
+ }"
510
+ />
511
+ </div>
512
+ </div>
513
+ <edge-shad-input
514
+ v-else
515
+ v-model="props.settings.favicon"
516
+ name="favicon"
517
+ label="Favicon URL"
518
+ placeholder="https://..."
519
+ class="w-full"
520
+ />
521
+ </TabsContent>
522
+ <TabsContent value="seo" class="pt-4">
523
+ <div class="space-y-4">
524
+ <p class="text-sm text-muted-foreground">
525
+ Default settings if the information is not entered on the page.
526
+ </p>
527
+ <edge-shad-input
528
+ v-model="props.settings.metaTitle"
529
+ label="Meta Title"
530
+ name="metaTitle"
531
+ />
532
+ <edge-shad-textarea
533
+ v-model="props.settings.metaDescription"
534
+ label="Meta Description"
535
+ name="metaDescription"
536
+ />
537
+ <div class="rounded-md border border-border/60 bg-muted/30 px-3 py-2 text-xs text-muted-foreground">
538
+ CMS tokens in double curly braces are replaced on the front end.
539
+ Example: <span v-pre class="font-semibold text-foreground">"{{cms-site}}"</span> for the site URL,
540
+ <span v-pre class="font-semibold text-foreground">"{{cms-url}}"</span> for the page URL, and
541
+ <span v-pre class="font-semibold text-foreground">"{{cms-logo}}"</span> for the logo URL. Keep the tokens intact.
542
+ </div>
543
+ <edge-cms-code-editor
544
+ v-model="props.settings.structuredData"
545
+ title="Structured Data (JSON-LD)"
546
+ language="json"
547
+ name="structuredData"
548
+ validate-json
549
+ height="300px"
550
+ class="mb-4 w-full"
551
+ />
552
+ </div>
553
+ </TabsContent>
554
+ <TabsContent value="tracking" class="pt-4">
555
+ <div class="space-y-4">
556
+ <p class="text-sm text-muted-foreground">
557
+ Add tracking IDs for this site.
558
+ </p>
559
+ <edge-shad-input
560
+ v-model="props.settings.trackingFacebookPixel"
561
+ label="Facebook Pixel ID"
562
+ name="trackingFacebookPixel"
563
+ placeholder="123456789012345"
564
+ />
565
+ <edge-shad-input
566
+ v-model="props.settings.trackingGoogleAnalytics"
567
+ label="Google Analytics ID"
568
+ name="trackingGoogleAnalytics"
569
+ placeholder="G-XXXXXXX"
570
+ />
571
+ <edge-shad-input
572
+ v-model="props.settings.trackingAdroll"
573
+ label="AdRoll ID"
574
+ name="trackingAdroll"
575
+ placeholder="ADROLL-ID"
576
+ />
577
+ </div>
578
+ </TabsContent>
579
+ <TabsContent value="social" class="pt-4">
580
+ <div class="space-y-4">
581
+ <p class="text-sm text-muted-foreground">
582
+ Add social media links for this site.
583
+ </p>
584
+ <edge-shad-input
585
+ v-model="props.settings.socialFacebook"
586
+ label="Facebook URL"
587
+ name="socialFacebook"
588
+ placeholder="https://facebook.com/yourpage"
589
+ />
590
+ <edge-shad-input
591
+ v-model="props.settings.socialInstagram"
592
+ label="Instagram URL"
593
+ name="socialInstagram"
594
+ placeholder="https://instagram.com/yourhandle"
595
+ />
596
+ <edge-shad-input
597
+ v-model="props.settings.socialTwitter"
598
+ label="X (Twitter) URL"
599
+ name="socialTwitter"
600
+ placeholder="https://x.com/yourhandle"
601
+ />
602
+ <edge-shad-input
603
+ v-model="props.settings.socialLinkedIn"
604
+ label="LinkedIn URL"
605
+ name="socialLinkedIn"
606
+ placeholder="https://linkedin.com/company/yourcompany"
607
+ />
608
+ <edge-shad-input
609
+ v-model="props.settings.socialYouTube"
610
+ label="YouTube URL"
611
+ name="socialYouTube"
612
+ placeholder="https://youtube.com/@yourchannel"
613
+ />
614
+ <edge-shad-input
615
+ v-model="props.settings.socialTikTok"
616
+ label="TikTok URL"
617
+ name="socialTikTok"
618
+ placeholder="https://tiktok.com/@yourhandle"
619
+ />
620
+ </div>
621
+ </TabsContent>
622
+ </Tabs>
623
+ </template>