@prenta/admin 0.98.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +17 -28
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/components/seo-score-bands.test.js +0 -3
- package/dist/__tests__/components/seo-score-bands.test.js.map +1 -1
- package/dist/__tests__/layout/sidebar-posts.test.js +6 -11
- package/dist/__tests__/layout/sidebar-posts.test.js.map +1 -1
- package/dist/__tests__/layout/sidebar-submenu.render.test.js +3 -2
- package/dist/__tests__/layout/sidebar-submenu.render.test.js.map +1 -1
- package/dist/__tests__/lib/page-editor-service-api.test.js +26 -23
- package/dist/__tests__/lib/page-editor-service-api.test.js.map +1 -1
- package/dist/__tests__/views/page-section-editor.test.js +0 -1
- package/dist/__tests__/views/page-section-editor.test.js.map +1 -1
- package/dist/__tests__/views/page-tab.render.test.js +0 -1
- package/dist/__tests__/views/page-tab.render.test.js.map +1 -1
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/layout/Sidebar.d.ts +3 -4
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +7 -38
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/lib/page-editor-service.d.ts +0 -7
- package/dist/lib/page-editor-service.d.ts.map +1 -1
- package/dist/lib/page-editor-service.js +9 -9
- package/dist/lib/page-editor-service.js.map +1 -1
- package/dist/prenta-admin.css +1 -1
- package/dist/views/PageEditor.d.ts +2 -2
- package/dist/views/PageEditor.js +2 -2
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.js +9 -15
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
- package/package.json +4 -4
- package/src/AdminRoot.tsx +18 -68
- package/src/__tests__/components/seo-score-bands.test.ts +0 -3
- package/src/__tests__/layout/sidebar-posts.test.ts +6 -11
- package/src/__tests__/layout/sidebar-submenu.render.test.tsx +3 -2
- package/src/__tests__/lib/page-editor-service-api.test.ts +27 -25
- package/src/__tests__/views/page-section-editor.test.tsx +0 -1
- package/src/__tests__/views/page-tab.render.test.tsx +0 -1
- package/src/index.ts +1 -11
- package/src/layout/Sidebar.tsx +6 -41
- package/src/lib/page-editor-service.ts +9 -16
- package/src/views/PageEditor.tsx +2 -2
- package/src/views/page-editor/PageSectionEditor.tsx +8 -37
- package/dist/__tests__/views/page-builder-block-renderer.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-block-renderer.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-block-renderer.render.test.js +0 -48
- package/dist/__tests__/views/page-builder-block-renderer.render.test.js.map +0 -1
- package/dist/__tests__/views/page-builder-form-picker.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-form-picker.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-form-picker.render.test.js +0 -44
- package/dist/__tests__/views/page-builder-form-picker.render.test.js.map +0 -1
- package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-sections-panel.render.test.js +0 -121
- package/dist/__tests__/views/page-builder-sections-panel.render.test.js.map +0 -1
- package/dist/__tests__/views/page-builder-seo-panel.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-seo-panel.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-seo-panel.render.test.js +0 -65
- package/dist/__tests__/views/page-builder-seo-panel.render.test.js.map +0 -1
- package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-template-seed.render.test.js +0 -48
- package/dist/__tests__/views/page-builder-template-seed.render.test.js.map +0 -1
- package/dist/__tests__/views/page-templates.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-templates.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-templates.render.test.js +0 -126
- package/dist/__tests__/views/page-templates.render.test.js.map +0 -1
- package/dist/hooks/useBuilderState.d.ts +0 -49
- package/dist/hooks/useBuilderState.d.ts.map +0 -1
- package/dist/hooks/useBuilderState.js +0 -238
- package/dist/hooks/useBuilderState.js.map +0 -1
- package/dist/views/page-builder/AIBlockAssist.d.ts +0 -9
- package/dist/views/page-builder/AIBlockAssist.d.ts.map +0 -1
- package/dist/views/page-builder/AIBlockAssist.js +0 -40
- package/dist/views/page-builder/AIBlockAssist.js.map +0 -1
- package/dist/views/page-builder/AIGenerateDialog.d.ts +0 -8
- package/dist/views/page-builder/AIGenerateDialog.d.ts.map +0 -1
- package/dist/views/page-builder/AIGenerateDialog.js +0 -173
- package/dist/views/page-builder/AIGenerateDialog.js.map +0 -1
- package/dist/views/page-builder/BlockEditor.d.ts +0 -11
- package/dist/views/page-builder/BlockEditor.d.ts.map +0 -1
- package/dist/views/page-builder/BlockEditor.js +0 -159
- package/dist/views/page-builder/BlockEditor.js.map +0 -1
- package/dist/views/page-builder/BlockPicker.d.ts +0 -7
- package/dist/views/page-builder/BlockPicker.d.ts.map +0 -1
- package/dist/views/page-builder/BlockPicker.js +0 -103
- package/dist/views/page-builder/BlockPicker.js.map +0 -1
- package/dist/views/page-builder/BottomBar.d.ts +0 -9
- package/dist/views/page-builder/BottomBar.d.ts.map +0 -1
- package/dist/views/page-builder/BottomBar.js +0 -13
- package/dist/views/page-builder/BottomBar.js.map +0 -1
- package/dist/views/page-builder/BuilderSectionsPanel.d.ts +0 -20
- package/dist/views/page-builder/BuilderSectionsPanel.d.ts.map +0 -1
- package/dist/views/page-builder/BuilderSectionsPanel.js +0 -105
- package/dist/views/page-builder/BuilderSectionsPanel.js.map +0 -1
- package/dist/views/page-builder/BuilderToolbar.d.ts +0 -21
- package/dist/views/page-builder/BuilderToolbar.d.ts.map +0 -1
- package/dist/views/page-builder/BuilderToolbar.js +0 -18
- package/dist/views/page-builder/BuilderToolbar.js.map +0 -1
- package/dist/views/page-builder/ContextPanel.d.ts +0 -20
- package/dist/views/page-builder/ContextPanel.d.ts.map +0 -1
- package/dist/views/page-builder/ContextPanel.js +0 -43
- package/dist/views/page-builder/ContextPanel.js.map +0 -1
- package/dist/views/page-builder/DesignScore.d.ts +0 -6
- package/dist/views/page-builder/DesignScore.d.ts.map +0 -1
- package/dist/views/page-builder/DesignScore.js +0 -93
- package/dist/views/page-builder/DesignScore.js.map +0 -1
- package/dist/views/page-builder/NodeSettings.d.ts +0 -12
- package/dist/views/page-builder/NodeSettings.d.ts.map +0 -1
- package/dist/views/page-builder/NodeSettings.js +0 -80
- package/dist/views/page-builder/NodeSettings.js.map +0 -1
- package/dist/views/page-builder/PageBuilder.d.ts +0 -10
- package/dist/views/page-builder/PageBuilder.d.ts.map +0 -1
- package/dist/views/page-builder/PageBuilder.js +0 -222
- package/dist/views/page-builder/PageBuilder.js.map +0 -1
- package/dist/views/page-builder/PageSettings.d.ts +0 -7
- package/dist/views/page-builder/PageSettings.d.ts.map +0 -1
- package/dist/views/page-builder/PageSettings.js +0 -27
- package/dist/views/page-builder/PageSettings.js.map +0 -1
- package/dist/views/page-builder/PageTemplates.d.ts +0 -5
- package/dist/views/page-builder/PageTemplates.d.ts.map +0 -1
- package/dist/views/page-builder/PageTemplates.js +0 -146
- package/dist/views/page-builder/PageTemplates.js.map +0 -1
- package/dist/views/page-builder/RelationshipField.d.ts +0 -15
- package/dist/views/page-builder/RelationshipField.d.ts.map +0 -1
- package/dist/views/page-builder/RelationshipField.js +0 -65
- package/dist/views/page-builder/RelationshipField.js.map +0 -1
- package/dist/views/page-builder/SEOPanel.d.ts +0 -9
- package/dist/views/page-builder/SEOPanel.d.ts.map +0 -1
- package/dist/views/page-builder/SEOPanel.js +0 -38
- package/dist/views/page-builder/SEOPanel.js.map +0 -1
- package/dist/views/page-builder/SavedSections.d.ts +0 -6
- package/dist/views/page-builder/SavedSections.d.ts.map +0 -1
- package/dist/views/page-builder/SavedSections.js +0 -141
- package/dist/views/page-builder/SavedSections.js.map +0 -1
- package/dist/views/page-builder/TemplatePicker.d.ts +0 -7
- package/dist/views/page-builder/TemplatePicker.d.ts.map +0 -1
- package/dist/views/page-builder/TemplatePicker.js +0 -68
- package/dist/views/page-builder/TemplatePicker.js.map +0 -1
- package/dist/views/page-builder/Widgets.d.ts +0 -13
- package/dist/views/page-builder/Widgets.d.ts.map +0 -1
- package/dist/views/page-builder/Widgets.js +0 -15
- package/dist/views/page-builder/Widgets.js.map +0 -1
- package/dist/views/page-builder/block-renderers/CTAPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/CTAPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/CTAPreview.js +0 -19
- package/dist/views/page-builder/block-renderers/CTAPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/CardsPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/CardsPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/CardsPreview.js +0 -22
- package/dist/views/page-builder/block-renderers/CardsPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/CodePreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/CodePreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/CodePreview.js +0 -12
- package/dist/views/page-builder/block-renderers/CodePreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/FAQPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/FAQPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/FAQPreview.js +0 -27
- package/dist/views/page-builder/block-renderers/FAQPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts +0 -6
- package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/FallbackPreview.js +0 -7
- package/dist/views/page-builder/block-renderers/FallbackPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/FormPreview.d.ts +0 -9
- package/dist/views/page-builder/block-renderers/FormPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/FormPreview.js +0 -52
- package/dist/views/page-builder/block-renderers/FormPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/GalleryPreview.js +0 -19
- package/dist/views/page-builder/block-renderers/GalleryPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/HeroPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/HeroPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/HeroPreview.js +0 -19
- package/dist/views/page-builder/block-renderers/HeroPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/ImagePreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/ImagePreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/ImagePreview.js +0 -17
- package/dist/views/page-builder/block-renderers/ImagePreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/TextPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/TextPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/TextPreview.js +0 -22
- package/dist/views/page-builder/block-renderers/TextPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/VideoPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/VideoPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/VideoPreview.js +0 -18
- package/dist/views/page-builder/block-renderers/VideoPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/index.d.ts +0 -9
- package/dist/views/page-builder/block-renderers/index.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/index.js +0 -25
- package/dist/views/page-builder/block-renderers/index.js.map +0 -1
- package/dist/views/page-builder/canvas/BlockRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/BlockRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/BlockRenderer.js +0 -20
- package/dist/views/page-builder/canvas/BlockRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/BuilderCanvas.d.ts +0 -10
- package/dist/views/page-builder/canvas/BuilderCanvas.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/BuilderCanvas.js +0 -26
- package/dist/views/page-builder/canvas/BuilderCanvas.js.map +0 -1
- package/dist/views/page-builder/canvas/ColumnRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/ColumnRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/ColumnRenderer.js +0 -32
- package/dist/views/page-builder/canvas/ColumnRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/ContainerRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/ContainerRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/ContainerRenderer.js +0 -29
- package/dist/views/page-builder/canvas/ContainerRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/RowRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/RowRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/RowRenderer.js +0 -28
- package/dist/views/page-builder/canvas/RowRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/SectionRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/SectionRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/SectionRenderer.js +0 -50
- package/dist/views/page-builder/canvas/SectionRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/index.d.ts +0 -3
- package/dist/views/page-builder/canvas/index.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/index.js +0 -2
- package/dist/views/page-builder/canvas/index.js.map +0 -1
- package/src/__tests__/views/page-builder-block-renderer.render.test.tsx +0 -66
- package/src/__tests__/views/page-builder-form-picker.render.test.tsx +0 -54
- package/src/__tests__/views/page-builder-sections-panel.render.test.tsx +0 -138
- package/src/__tests__/views/page-builder-seo-panel.render.test.tsx +0 -77
- package/src/__tests__/views/page-builder-template-seed.render.test.tsx +0 -59
- package/src/__tests__/views/page-templates.render.test.tsx +0 -182
- package/src/hooks/useBuilderState.ts +0 -361
- package/src/views/page-builder/AIBlockAssist.tsx +0 -64
- package/src/views/page-builder/AIGenerateDialog.tsx +0 -593
- package/src/views/page-builder/BlockEditor.tsx +0 -523
- package/src/views/page-builder/BlockPicker.tsx +0 -324
- package/src/views/page-builder/BottomBar.tsx +0 -65
- package/src/views/page-builder/BuilderSectionsPanel.tsx +0 -304
- package/src/views/page-builder/BuilderToolbar.tsx +0 -221
- package/src/views/page-builder/ContextPanel.tsx +0 -144
- package/src/views/page-builder/DesignScore.tsx +0 -251
- package/src/views/page-builder/NodeSettings.tsx +0 -500
- package/src/views/page-builder/PageBuilder.tsx +0 -405
- package/src/views/page-builder/PageSettings.tsx +0 -162
- package/src/views/page-builder/PageTemplates.tsx +0 -435
- package/src/views/page-builder/RelationshipField.tsx +0 -113
- package/src/views/page-builder/SEOPanel.tsx +0 -222
- package/src/views/page-builder/SavedSections.tsx +0 -480
- package/src/views/page-builder/TemplatePicker.tsx +0 -197
- package/src/views/page-builder/Widgets.tsx +0 -44
- package/src/views/page-builder/block-renderers/CTAPreview.tsx +0 -79
- package/src/views/page-builder/block-renderers/CardsPreview.tsx +0 -69
- package/src/views/page-builder/block-renderers/CodePreview.tsx +0 -46
- package/src/views/page-builder/block-renderers/FAQPreview.tsx +0 -87
- package/src/views/page-builder/block-renderers/FallbackPreview.tsx +0 -18
- package/src/views/page-builder/block-renderers/FormPreview.tsx +0 -117
- package/src/views/page-builder/block-renderers/GalleryPreview.tsx +0 -87
- package/src/views/page-builder/block-renderers/HeroPreview.tsx +0 -90
- package/src/views/page-builder/block-renderers/ImagePreview.tsx +0 -54
- package/src/views/page-builder/block-renderers/TextPreview.tsx +0 -81
- package/src/views/page-builder/block-renderers/VideoPreview.tsx +0 -73
- package/src/views/page-builder/block-renderers/index.ts +0 -34
- package/src/views/page-builder/canvas/BlockRenderer.tsx +0 -56
- package/src/views/page-builder/canvas/BuilderCanvas.tsx +0 -87
- package/src/views/page-builder/canvas/ColumnRenderer.tsx +0 -82
- package/src/views/page-builder/canvas/ContainerRenderer.tsx +0 -67
- package/src/views/page-builder/canvas/RowRenderer.tsx +0 -68
- package/src/views/page-builder/canvas/SectionRenderer.tsx +0 -93
- package/src/views/page-builder/canvas/index.ts +0 -2
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect, useCallback } from 'react'
|
|
4
|
-
import {
|
|
5
|
-
X,
|
|
6
|
-
Loader2,
|
|
7
|
-
AlertTriangle,
|
|
8
|
-
Layout,
|
|
9
|
-
FileText,
|
|
10
|
-
MapPin,
|
|
11
|
-
Phone,
|
|
12
|
-
BookOpen,
|
|
13
|
-
File,
|
|
14
|
-
} from 'lucide-react'
|
|
15
|
-
import type { LucideIcon } from 'lucide-react'
|
|
16
|
-
import { cmsApi } from '../../lib/api.js'
|
|
17
|
-
import { createEmptyPage } from '@prenta/core/page-builder'
|
|
18
|
-
import type { PageNode } from '@prenta/core'
|
|
19
|
-
|
|
20
|
-
export interface TemplatePickerProps {
|
|
21
|
-
onSelect: (templateTree: PageNode) => void
|
|
22
|
-
onClose: () => void
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface PageTemplate {
|
|
26
|
-
id: string
|
|
27
|
-
name: string
|
|
28
|
-
description: string
|
|
29
|
-
category: string
|
|
30
|
-
tree: PageNode
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const categoryIcons: Record<string, LucideIcon> = {
|
|
34
|
-
landing: Layout,
|
|
35
|
-
blog: FileText,
|
|
36
|
-
contact: MapPin,
|
|
37
|
-
portfolio: BookOpen,
|
|
38
|
-
mobile: Phone,
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const categoryColors: Record<string, string> = {
|
|
42
|
-
landing: 'bg-primary/10 text-primary',
|
|
43
|
-
blog: 'bg-accent text-foreground',
|
|
44
|
-
contact: 'bg-primary/10 text-primary',
|
|
45
|
-
portfolio: 'bg-accent text-foreground',
|
|
46
|
-
mobile: 'bg-primary/10 text-primary',
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function resolveIcon(category: string): LucideIcon {
|
|
50
|
-
return categoryIcons[category] ?? File
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function resolveBadgeColor(category: string): string {
|
|
54
|
-
return categoryColors[category] ?? 'bg-muted text-muted-foreground'
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function TemplatePicker({ onSelect, onClose }: TemplatePickerProps) {
|
|
58
|
-
const [templates, setTemplates] = useState<PageTemplate[]>([])
|
|
59
|
-
const [loading, setLoading] = useState(true)
|
|
60
|
-
const [error, setError] = useState<string | null>(null)
|
|
61
|
-
|
|
62
|
-
const fetchTemplates = useCallback(async () => {
|
|
63
|
-
setLoading(true)
|
|
64
|
-
setError(null)
|
|
65
|
-
const res = await cmsApi<PageTemplate[]>('/page-templates')
|
|
66
|
-
if (res.error) {
|
|
67
|
-
setError(res.error)
|
|
68
|
-
} else {
|
|
69
|
-
setTemplates(res.data ?? [])
|
|
70
|
-
}
|
|
71
|
-
setLoading(false)
|
|
72
|
-
}, [])
|
|
73
|
-
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
fetchTemplates()
|
|
76
|
-
}, [fetchTemplates])
|
|
77
|
-
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
function handleKeyDown(e: KeyboardEvent) {
|
|
80
|
-
if (e.key === 'Escape') onClose()
|
|
81
|
-
}
|
|
82
|
-
document.addEventListener('keydown', handleKeyDown)
|
|
83
|
-
return () => document.removeEventListener('keydown', handleKeyDown)
|
|
84
|
-
}, [onClose])
|
|
85
|
-
|
|
86
|
-
function handleBlankPage() {
|
|
87
|
-
onSelect(createEmptyPage())
|
|
88
|
-
onClose()
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function handleTemplateSelect(template: PageTemplate) {
|
|
92
|
-
onSelect(template.tree)
|
|
93
|
-
onClose()
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<div
|
|
98
|
-
className="fixed inset-0 z-50 flex items-center justify-center"
|
|
99
|
-
role="dialog"
|
|
100
|
-
aria-modal="true"
|
|
101
|
-
aria-label="Choose a template"
|
|
102
|
-
>
|
|
103
|
-
<div
|
|
104
|
-
className="motion-safe:animate-in motion-safe:fade-in-0 absolute inset-0 bg-black/50"
|
|
105
|
-
onClick={onClose}
|
|
106
|
-
aria-hidden="true"
|
|
107
|
-
/>
|
|
108
|
-
|
|
109
|
-
<div className="bg-card border-border relative z-10 flex max-h-[80vh] w-full max-w-2xl flex-col overflow-hidden rounded-xl border shadow-2xl">
|
|
110
|
-
<div className="flex items-center justify-between px-5 pt-5 pb-3">
|
|
111
|
-
<h2 className="text-foreground text-lg font-medium">Choose a Template</h2>
|
|
112
|
-
<button
|
|
113
|
-
type="button"
|
|
114
|
-
onClick={onClose}
|
|
115
|
-
className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-md p-1.5 transition-colors"
|
|
116
|
-
aria-label="Close"
|
|
117
|
-
>
|
|
118
|
-
<X size={18} />
|
|
119
|
-
</button>
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
<div className="flex-1 overflow-y-auto px-5 pb-5">
|
|
123
|
-
{loading && (
|
|
124
|
-
<div className="flex items-center justify-center py-16">
|
|
125
|
-
<Loader2 className="text-muted-foreground h-6 w-6 animate-spin" />
|
|
126
|
-
</div>
|
|
127
|
-
)}
|
|
128
|
-
|
|
129
|
-
{error && !loading && (
|
|
130
|
-
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
131
|
-
<AlertTriangle size={24} className="text-destructive mb-2" />
|
|
132
|
-
<p className="text-muted-foreground mb-3 text-sm">{error}</p>
|
|
133
|
-
<button
|
|
134
|
-
type="button"
|
|
135
|
-
onClick={fetchTemplates}
|
|
136
|
-
className="text-foreground border-border hover:bg-accent rounded-md border px-3 py-1.5 text-sm font-medium transition-colors"
|
|
137
|
-
>
|
|
138
|
-
Retry
|
|
139
|
-
</button>
|
|
140
|
-
</div>
|
|
141
|
-
)}
|
|
142
|
-
|
|
143
|
-
{!loading && !error && (
|
|
144
|
-
<div
|
|
145
|
-
className="grid grid-cols-2 gap-3 md:grid-cols-3"
|
|
146
|
-
role="list"
|
|
147
|
-
aria-label="Available templates"
|
|
148
|
-
>
|
|
149
|
-
<button
|
|
150
|
-
type="button"
|
|
151
|
-
role="listitem"
|
|
152
|
-
onClick={handleBlankPage}
|
|
153
|
-
className="border-border hover:border-primary bg-card group focus:ring-ring flex cursor-pointer flex-col items-center gap-2 rounded-lg border p-4 text-left transition-colors focus:ring-2 focus:outline-none"
|
|
154
|
-
>
|
|
155
|
-
<div className="bg-muted text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary flex h-10 w-10 items-center justify-center rounded-md transition-colors">
|
|
156
|
-
<File size={20} />
|
|
157
|
-
</div>
|
|
158
|
-
<p className="text-foreground text-center text-sm font-medium">Blank Page</p>
|
|
159
|
-
<p className="text-muted-foreground text-center text-xs">Start from scratch</p>
|
|
160
|
-
</button>
|
|
161
|
-
|
|
162
|
-
{templates.map((template) => {
|
|
163
|
-
const Icon = resolveIcon(template.category)
|
|
164
|
-
const badgeColor = resolveBadgeColor(template.category)
|
|
165
|
-
|
|
166
|
-
return (
|
|
167
|
-
<button
|
|
168
|
-
key={template.id}
|
|
169
|
-
type="button"
|
|
170
|
-
role="listitem"
|
|
171
|
-
onClick={() => handleTemplateSelect(template)}
|
|
172
|
-
className="border-border hover:border-primary bg-card group focus:ring-ring flex cursor-pointer flex-col items-center gap-2 rounded-lg border p-4 text-left transition-colors focus:ring-2 focus:outline-none"
|
|
173
|
-
>
|
|
174
|
-
<div className="bg-accent text-foreground group-hover:bg-primary/10 group-hover:text-primary flex h-10 w-10 items-center justify-center rounded-md transition-colors">
|
|
175
|
-
<Icon size={20} />
|
|
176
|
-
</div>
|
|
177
|
-
<p className="text-foreground w-full truncate text-center text-sm font-medium">
|
|
178
|
-
{template.name}
|
|
179
|
-
</p>
|
|
180
|
-
{template.description && (
|
|
181
|
-
<p className="text-muted-foreground line-clamp-2 text-center text-xs">
|
|
182
|
-
{template.description}
|
|
183
|
-
</p>
|
|
184
|
-
)}
|
|
185
|
-
<span className={`inline-block rounded-full px-2 py-0.5 text-xs ${badgeColor}`}>
|
|
186
|
-
{template.category}
|
|
187
|
-
</span>
|
|
188
|
-
</button>
|
|
189
|
-
)
|
|
190
|
-
})}
|
|
191
|
-
</div>
|
|
192
|
-
)}
|
|
193
|
-
</div>
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
196
|
-
)
|
|
197
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { LayoutGrid } from 'lucide-react'
|
|
4
|
-
|
|
5
|
-
export interface WidgetsProps {
|
|
6
|
-
onNavigate?: (path: string) => void
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* /widgets — placeholder for the embeddable Widgets library.
|
|
11
|
-
*
|
|
12
|
-
* Widgets are reusable, embeddable components (forms, CTAs, banners)
|
|
13
|
-
* that can be dropped into pages and saved sections. The management UI
|
|
14
|
-
* ships in a later release; this view establishes the route and nav
|
|
15
|
-
* entry with an honest empty state rather than a dead link.
|
|
16
|
-
*/
|
|
17
|
-
export function Widgets({ onNavigate }: WidgetsProps) {
|
|
18
|
-
return (
|
|
19
|
-
<div className="p-4 pr-8">
|
|
20
|
-
<div className="mb-4">
|
|
21
|
-
<h1 className="text-foreground mb-1">Widgets</h1>
|
|
22
|
-
<p className="text-muted-foreground text-sm">
|
|
23
|
-
Reusable, embeddable components for pages and saved sections.
|
|
24
|
-
</p>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<div className="border-border bg-card rounded-lg border p-8 text-center">
|
|
28
|
-
<LayoutGrid className="text-muted-foreground mx-auto mb-3 h-8 w-8" aria-hidden />
|
|
29
|
-
<h2 className="text-foreground mb-1 text-lg font-medium">Widgets are coming soon</h2>
|
|
30
|
-
<p className="text-muted-foreground mx-auto mb-4 max-w-md text-sm">
|
|
31
|
-
The widget library lets you build embeddable components once and reuse them across pages.
|
|
32
|
-
In the meantime, use Saved Sections for reusable content blocks.
|
|
33
|
-
</p>
|
|
34
|
-
<button
|
|
35
|
-
type="button"
|
|
36
|
-
onClick={() => onNavigate?.('/saved-sections')}
|
|
37
|
-
className="bg-primary text-primary-foreground rounded-md px-4 py-2 text-sm font-medium hover:opacity-90"
|
|
38
|
-
>
|
|
39
|
-
View Saved Sections
|
|
40
|
-
</button>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
)
|
|
44
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import type { BlockPreviewProps } from './index.js'
|
|
4
|
-
|
|
5
|
-
export function CTAPreview({ data, variant = 'banner' }: BlockPreviewProps) {
|
|
6
|
-
const heading = (data.heading as string) || ''
|
|
7
|
-
const body = (data.body as string) || ''
|
|
8
|
-
const buttonText = (data.buttonText as string) || ''
|
|
9
|
-
|
|
10
|
-
const button = (
|
|
11
|
-
<span className="bg-primary text-primary-foreground inline-block rounded-md px-4 py-2 text-sm">
|
|
12
|
-
{buttonText || 'Click Here'}
|
|
13
|
-
</span>
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
if (variant === 'inline') {
|
|
17
|
-
return (
|
|
18
|
-
<div className="border-border flex items-center justify-between gap-4 rounded-md border p-4">
|
|
19
|
-
<div className="flex-1">
|
|
20
|
-
<p className="text-foreground text-sm font-medium">
|
|
21
|
-
{heading || <span className="text-muted-foreground">CTA Heading</span>}
|
|
22
|
-
</p>
|
|
23
|
-
{body && <p className="text-muted-foreground mt-1 text-xs">{body}</p>}
|
|
24
|
-
</div>
|
|
25
|
-
{button}
|
|
26
|
-
</div>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (variant === 'floating') {
|
|
31
|
-
return (
|
|
32
|
-
<div className="border-border bg-card mx-auto max-w-md rounded-lg border p-6 shadow-md">
|
|
33
|
-
<div className="text-center">
|
|
34
|
-
<h3 className="text-foreground text-lg font-medium">
|
|
35
|
-
{heading || <span className="text-muted-foreground">CTA Heading</span>}
|
|
36
|
-
</h3>
|
|
37
|
-
{(body || !heading) && (
|
|
38
|
-
<p className="text-muted-foreground mt-2 text-sm">
|
|
39
|
-
{body || 'Supporting text for the call to action'}
|
|
40
|
-
</p>
|
|
41
|
-
)}
|
|
42
|
-
<div className="mt-4">{button}</div>
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (variant === 'split') {
|
|
49
|
-
return (
|
|
50
|
-
<div className="border-border flex items-center gap-6 rounded-md border p-6">
|
|
51
|
-
<div className="flex-1">
|
|
52
|
-
<h3 className="text-foreground text-lg font-medium">
|
|
53
|
-
{heading || <span className="text-muted-foreground">CTA Heading</span>}
|
|
54
|
-
</h3>
|
|
55
|
-
{(body || !heading) && (
|
|
56
|
-
<p className="text-muted-foreground mt-2 text-sm">
|
|
57
|
-
{body || 'Supporting text for the call to action'}
|
|
58
|
-
</p>
|
|
59
|
-
)}
|
|
60
|
-
</div>
|
|
61
|
-
<div className="shrink-0">{button}</div>
|
|
62
|
-
</div>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<div className="border-border bg-accent rounded-md border p-6 text-center">
|
|
68
|
-
<h3 className="text-foreground text-lg font-medium">
|
|
69
|
-
{heading || <span className="text-muted-foreground">CTA Heading</span>}
|
|
70
|
-
</h3>
|
|
71
|
-
{(body || !heading) && (
|
|
72
|
-
<p className="text-muted-foreground mt-2 text-sm">
|
|
73
|
-
{body || 'Supporting text for the call to action'}
|
|
74
|
-
</p>
|
|
75
|
-
)}
|
|
76
|
-
<div className="mt-4">{button}</div>
|
|
77
|
-
</div>
|
|
78
|
-
)
|
|
79
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { Image } from 'lucide-react'
|
|
4
|
-
import type { BlockPreviewProps } from './index.js'
|
|
5
|
-
|
|
6
|
-
interface CardItem {
|
|
7
|
-
title?: string
|
|
8
|
-
description?: string
|
|
9
|
-
image?: string
|
|
10
|
-
link?: string
|
|
11
|
-
icon?: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function CardsPreview({ data, variant = 'grid-3' }: BlockPreviewProps) {
|
|
15
|
-
const items = (data.items as CardItem[]) || []
|
|
16
|
-
|
|
17
|
-
const placeholderItems: CardItem[] =
|
|
18
|
-
items.length > 0
|
|
19
|
-
? items
|
|
20
|
-
: [
|
|
21
|
-
{ title: 'Card Title', description: 'Card description text' },
|
|
22
|
-
{ title: 'Card Title', description: 'Card description text' },
|
|
23
|
-
{ title: 'Card Title', description: 'Card description text' },
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
const gridClass =
|
|
27
|
-
variant === 'grid-2'
|
|
28
|
-
? 'grid grid-cols-2 gap-3'
|
|
29
|
-
: variant === 'grid-4'
|
|
30
|
-
? 'grid grid-cols-4 gap-3'
|
|
31
|
-
: variant === 'horizontal'
|
|
32
|
-
? 'flex gap-3 overflow-hidden'
|
|
33
|
-
: 'grid grid-cols-3 gap-3'
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div className="border-border rounded-md border p-4">
|
|
37
|
-
<div className={gridClass}>
|
|
38
|
-
{placeholderItems.map((item, i) => (
|
|
39
|
-
<div
|
|
40
|
-
key={i}
|
|
41
|
-
className={`border-border bg-card flex flex-col overflow-hidden rounded-md border ${variant === 'horizontal' ? 'min-w-40' : ''}`}
|
|
42
|
-
>
|
|
43
|
-
<div className="bg-muted flex h-20 items-center justify-center">
|
|
44
|
-
{item.image ? (
|
|
45
|
-
<img
|
|
46
|
-
src={item.image}
|
|
47
|
-
alt={item.title || ''}
|
|
48
|
-
className="h-full w-full object-cover"
|
|
49
|
-
/>
|
|
50
|
-
) : (
|
|
51
|
-
<Image size={20} className="text-muted-foreground" />
|
|
52
|
-
)}
|
|
53
|
-
</div>
|
|
54
|
-
<div className="p-3">
|
|
55
|
-
<p className="text-foreground text-sm font-medium">
|
|
56
|
-
{item.title || <span className="text-muted-foreground">Card Title</span>}
|
|
57
|
-
</p>
|
|
58
|
-
{item.description && (
|
|
59
|
-
<p className="text-muted-foreground mt-1 line-clamp-2 text-xs">
|
|
60
|
-
{item.description}
|
|
61
|
-
</p>
|
|
62
|
-
)}
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
))}
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { Code, Globe, Terminal } from 'lucide-react'
|
|
4
|
-
import type { BlockPreviewProps } from './index.js'
|
|
5
|
-
|
|
6
|
-
export function CodePreview({ data, variant = 'embed' }: BlockPreviewProps) {
|
|
7
|
-
const content = (data.content as string) || ''
|
|
8
|
-
const language = (data.language as string) || ''
|
|
9
|
-
const sandboxed = data.sandboxed as boolean | undefined
|
|
10
|
-
|
|
11
|
-
const truncated = content.length > 200 ? content.slice(0, 200) + '…' : content
|
|
12
|
-
|
|
13
|
-
const icon =
|
|
14
|
-
variant === 'html' ? (
|
|
15
|
-
<Globe size={14} className="text-muted-foreground" />
|
|
16
|
-
) : variant === 'script' ? (
|
|
17
|
-
<Terminal size={14} className="text-muted-foreground" />
|
|
18
|
-
) : (
|
|
19
|
-
<Code size={14} className="text-muted-foreground" />
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<div className="border-border overflow-hidden rounded-md border">
|
|
24
|
-
<div className="border-border bg-card flex items-center gap-2 border-b px-3 py-2">
|
|
25
|
-
{icon}
|
|
26
|
-
<span className="text-muted-foreground text-xs">{language || variant}</span>
|
|
27
|
-
{sandboxed && (
|
|
28
|
-
<span className="bg-muted text-muted-foreground ml-auto rounded px-1.5 py-0.5 text-xs">
|
|
29
|
-
sandboxed
|
|
30
|
-
</span>
|
|
31
|
-
)}
|
|
32
|
-
</div>
|
|
33
|
-
<div className="bg-card p-4">
|
|
34
|
-
{truncated ? (
|
|
35
|
-
<pre className="text-foreground overflow-hidden font-mono text-xs whitespace-pre-wrap">
|
|
36
|
-
{truncated}
|
|
37
|
-
</pre>
|
|
38
|
-
) : (
|
|
39
|
-
<p className="text-muted-foreground font-mono text-xs italic">
|
|
40
|
-
// Code content goes here…
|
|
41
|
-
</p>
|
|
42
|
-
)}
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
)
|
|
46
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { ChevronDown } from 'lucide-react'
|
|
4
|
-
import type { BlockPreviewProps } from './index.js'
|
|
5
|
-
|
|
6
|
-
interface FAQItem {
|
|
7
|
-
question?: string
|
|
8
|
-
answer?: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function FAQPreview({ data, variant = 'accordion' }: BlockPreviewProps) {
|
|
12
|
-
const items = (data.items as FAQItem[]) || []
|
|
13
|
-
|
|
14
|
-
const placeholderItems: FAQItem[] =
|
|
15
|
-
items.length > 0
|
|
16
|
-
? items
|
|
17
|
-
: [
|
|
18
|
-
{ question: 'What is this?', answer: 'Answer to the first question goes here.' },
|
|
19
|
-
{ question: 'How does it work?', answer: 'Answer to the second question goes here.' },
|
|
20
|
-
{
|
|
21
|
-
question: 'Where can I learn more?',
|
|
22
|
-
answer: 'Answer to the third question goes here.',
|
|
23
|
-
},
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
if (variant === 'two-column') {
|
|
27
|
-
const mid = Math.ceil(placeholderItems.length / 2)
|
|
28
|
-
const col1 = placeholderItems.slice(0, mid)
|
|
29
|
-
const col2 = placeholderItems.slice(mid)
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div className="border-border rounded-md border p-4">
|
|
33
|
-
<div className="grid grid-cols-2 gap-4">
|
|
34
|
-
{[col1, col2].map((column, colIdx) => (
|
|
35
|
-
<div key={colIdx} className="flex flex-col gap-3">
|
|
36
|
-
{column.map((item, i) => (
|
|
37
|
-
<div key={i} className="border-border rounded-md border p-3">
|
|
38
|
-
<p className="text-foreground text-sm font-medium">
|
|
39
|
-
{item.question || 'Question?'}
|
|
40
|
-
</p>
|
|
41
|
-
<p className="text-muted-foreground mt-1 line-clamp-2 text-xs">
|
|
42
|
-
{item.answer || 'Answer text…'}
|
|
43
|
-
</p>
|
|
44
|
-
</div>
|
|
45
|
-
))}
|
|
46
|
-
</div>
|
|
47
|
-
))}
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (variant === 'list') {
|
|
54
|
-
return (
|
|
55
|
-
<div className="border-border rounded-md border p-4">
|
|
56
|
-
<div className="flex flex-col gap-3">
|
|
57
|
-
{placeholderItems.map((item, i) => (
|
|
58
|
-
<div key={i} className="border-border border-b pb-3 last:border-b-0 last:pb-0">
|
|
59
|
-
<p className="text-foreground text-sm font-medium">{item.question || 'Question?'}</p>
|
|
60
|
-
<p className="text-muted-foreground mt-1 line-clamp-2 text-xs">
|
|
61
|
-
{item.answer || 'Answer text…'}
|
|
62
|
-
</p>
|
|
63
|
-
</div>
|
|
64
|
-
))}
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
<div className="border-border rounded-md border p-4">
|
|
72
|
-
<div className="flex flex-col gap-2">
|
|
73
|
-
{placeholderItems.map((item, i) => (
|
|
74
|
-
<div key={i} className="border-border rounded-md border p-3">
|
|
75
|
-
<div className="flex items-center justify-between">
|
|
76
|
-
<p className="text-foreground text-sm font-medium">{item.question || 'Question?'}</p>
|
|
77
|
-
<ChevronDown size={14} className="text-muted-foreground shrink-0" />
|
|
78
|
-
</div>
|
|
79
|
-
{i === 0 && (
|
|
80
|
-
<p className="text-muted-foreground mt-2 text-xs">{item.answer || 'Answer text…'}</p>
|
|
81
|
-
)}
|
|
82
|
-
</div>
|
|
83
|
-
))}
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
)
|
|
87
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { Box } from 'lucide-react'
|
|
4
|
-
|
|
5
|
-
export interface FallbackPreviewProps {
|
|
6
|
-
blockType: string
|
|
7
|
-
data: Record<string, unknown>
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function FallbackPreview({ blockType }: FallbackPreviewProps) {
|
|
11
|
-
return (
|
|
12
|
-
<div className="border-border bg-muted/50 flex flex-col items-center justify-center gap-2 rounded-md border border-dashed p-8">
|
|
13
|
-
<Box size={24} className="text-muted-foreground" />
|
|
14
|
-
<p className="text-muted-foreground text-sm font-medium">{blockType}</p>
|
|
15
|
-
<p className="text-muted-foreground text-xs">No preview available</p>
|
|
16
|
-
</div>
|
|
17
|
-
)
|
|
18
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
4
|
-
import { FileText, Mail, Send } from 'lucide-react'
|
|
5
|
-
import { cmsApi } from '../../../lib/api.js'
|
|
6
|
-
import type { BlockPreviewProps } from './index.js'
|
|
7
|
-
|
|
8
|
-
interface PreviewField {
|
|
9
|
-
key: string
|
|
10
|
-
label: string
|
|
11
|
-
type: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface PreviewForm {
|
|
15
|
-
name: string
|
|
16
|
-
fields: PreviewField[]
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Canvas preview for the form block. When a form is linked we fetch its real
|
|
21
|
-
* name + fields so the editor sees what will render; otherwise we fall back to
|
|
22
|
-
* a generic skeleton. Network failures degrade to the skeleton rather than
|
|
23
|
-
* breaking the canvas.
|
|
24
|
-
*/
|
|
25
|
-
export function FormPreview({ data, variant = 'contact' }: BlockPreviewProps) {
|
|
26
|
-
const formId = (data.formId as string) || ''
|
|
27
|
-
const [form, setForm] = useState<PreviewForm | null>(null)
|
|
28
|
-
const [loading, setLoading] = useState(false)
|
|
29
|
-
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
if (!formId) {
|
|
32
|
-
setForm(null)
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
let cancelled = false
|
|
36
|
-
setLoading(true)
|
|
37
|
-
cmsApi<{ name?: string; fields?: PreviewField[] }>(`/forms/${encodeURIComponent(formId)}`)
|
|
38
|
-
.then((res) => {
|
|
39
|
-
if (cancelled) return
|
|
40
|
-
if (res.data?.name) {
|
|
41
|
-
setForm({ name: res.data.name, fields: res.data.fields ?? [] })
|
|
42
|
-
} else {
|
|
43
|
-
setForm(null)
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
.finally(() => {
|
|
47
|
-
if (!cancelled) setLoading(false)
|
|
48
|
-
})
|
|
49
|
-
return () => {
|
|
50
|
-
cancelled = true
|
|
51
|
-
}
|
|
52
|
-
}, [formId])
|
|
53
|
-
|
|
54
|
-
if (!formId) {
|
|
55
|
-
return (
|
|
56
|
-
<div className="border-border bg-muted/30 rounded-md border border-dashed p-6 text-center">
|
|
57
|
-
<FileText size={20} className="text-muted-foreground mx-auto" />
|
|
58
|
-
<p className="text-muted-foreground mt-2 text-sm">No form selected</p>
|
|
59
|
-
<p className="text-muted-foreground/70 text-xs">Pick a form in the block settings</p>
|
|
60
|
-
</div>
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const heading = form?.name ?? (loading ? 'Loading form…' : 'Contact Form')
|
|
65
|
-
const fields = form?.fields ?? []
|
|
66
|
-
|
|
67
|
-
if (variant === 'newsletter') {
|
|
68
|
-
return (
|
|
69
|
-
<div className="border-border rounded-md border p-6 text-center">
|
|
70
|
-
<Mail size={24} className="text-muted-foreground mx-auto" />
|
|
71
|
-
<p className="text-foreground mt-3 text-sm font-medium">
|
|
72
|
-
{form?.name ?? 'Newsletter Signup'}
|
|
73
|
-
</p>
|
|
74
|
-
<div className="mx-auto mt-4 flex max-w-xs gap-2">
|
|
75
|
-
<div className="border-border bg-input-background h-9 flex-1 rounded-md border" />
|
|
76
|
-
<div className="bg-primary flex h-9 items-center rounded-md px-3">
|
|
77
|
-
<Send size={14} className="text-primary-foreground" />
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<div className="border-border rounded-md border p-5">
|
|
86
|
-
<div className="flex items-center gap-2">
|
|
87
|
-
<FileText size={16} className="text-muted-foreground" />
|
|
88
|
-
<p className="text-foreground text-sm font-medium">{heading}</p>
|
|
89
|
-
</div>
|
|
90
|
-
<div className="mt-3 flex flex-col gap-2">
|
|
91
|
-
{fields.length > 0 ? (
|
|
92
|
-
fields.slice(0, 5).map((field) => (
|
|
93
|
-
<div key={field.key}>
|
|
94
|
-
<p className="text-muted-foreground mb-1 text-xs">{field.label || field.key}</p>
|
|
95
|
-
<div
|
|
96
|
-
className={`border-border bg-input-background rounded-md border ${
|
|
97
|
-
field.type === 'textarea' ? 'h-16' : 'h-8'
|
|
98
|
-
}`}
|
|
99
|
-
/>
|
|
100
|
-
</div>
|
|
101
|
-
))
|
|
102
|
-
) : (
|
|
103
|
-
<>
|
|
104
|
-
<div className="border-border bg-input-background h-8 rounded-md border" />
|
|
105
|
-
<div className="border-border bg-input-background h-8 rounded-md border" />
|
|
106
|
-
<div className="border-border bg-input-background h-20 rounded-md border" />
|
|
107
|
-
</>
|
|
108
|
-
)}
|
|
109
|
-
</div>
|
|
110
|
-
<div className="mt-3">
|
|
111
|
-
<span className="bg-primary text-primary-foreground inline-block rounded-md px-4 py-2 text-sm">
|
|
112
|
-
{variant === 'custom' ? 'Submit' : 'Send Message'}
|
|
113
|
-
</span>
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
)
|
|
117
|
-
}
|