@prenta/admin 0.98.0 → 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 +12 -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 -10
- 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-audit-input.test.js +77 -1
- package/dist/__tests__/lib/page-audit-input.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-audit-input.d.ts +14 -1
- package/dist/lib/page-audit-input.d.ts.map +1 -1
- package/dist/lib/page-audit-input.js +19 -0
- package/dist/lib/page-audit-input.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 -10
- 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-audit-input.test.ts +91 -1
- 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-audit-input.ts +36 -1
- 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-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 -10
- package/dist/views/page-builder/SEOPanel.d.ts.map +0 -1
- package/dist/views/page-builder/SEOPanel.js +0 -103
- 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-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 -145
- 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 -464
- 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,480 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect, useCallback } from 'react'
|
|
4
|
-
import {
|
|
5
|
-
Plus,
|
|
6
|
-
ArrowLeft,
|
|
7
|
-
Edit,
|
|
8
|
-
Trash2,
|
|
9
|
-
Copy,
|
|
10
|
-
Layers,
|
|
11
|
-
Loader2,
|
|
12
|
-
AlertTriangle,
|
|
13
|
-
Calendar,
|
|
14
|
-
Hash,
|
|
15
|
-
} from 'lucide-react'
|
|
16
|
-
import { toast } from 'sonner'
|
|
17
|
-
import { cmsApi } from '../../lib/api.js'
|
|
18
|
-
import { createSection, createContainer, createRow, createColumn } from '@prenta/core/page-builder'
|
|
19
|
-
|
|
20
|
-
export interface SavedSectionsProps {
|
|
21
|
-
onNavigate: (path: string) => void
|
|
22
|
-
config: any
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface SavedSection {
|
|
26
|
-
id: string
|
|
27
|
-
name: string
|
|
28
|
-
description: string
|
|
29
|
-
category: string
|
|
30
|
-
tree: Record<string, unknown> | null
|
|
31
|
-
usageCount: number
|
|
32
|
-
createdAt: string
|
|
33
|
-
updatedAt: string
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type ViewState = { mode: 'list' } | { mode: 'create' } | { mode: 'edit'; section: SavedSection }
|
|
37
|
-
|
|
38
|
-
const CATEGORIES = ['header', 'footer', 'content', 'sidebar'] as const
|
|
39
|
-
type Category = (typeof CATEGORIES)[number]
|
|
40
|
-
|
|
41
|
-
const CATEGORY_COLORS: Record<string, string> = {
|
|
42
|
-
header: 'bg-primary/10 text-primary',
|
|
43
|
-
footer: 'bg-accent text-foreground',
|
|
44
|
-
content: 'bg-muted text-muted-foreground',
|
|
45
|
-
sidebar: 'bg-primary/10 text-primary',
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function formatDate(dateStr: string): string {
|
|
49
|
-
return new Date(dateStr).toLocaleDateString(undefined, {
|
|
50
|
-
month: 'short',
|
|
51
|
-
day: 'numeric',
|
|
52
|
-
year: 'numeric',
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function createEmptySectionTree(): Record<string, unknown> {
|
|
57
|
-
const col = createColumn(100)
|
|
58
|
-
const row = createRow([col])
|
|
59
|
-
const container = createContainer()
|
|
60
|
-
;(container as unknown as { children: unknown[] }).children = [row]
|
|
61
|
-
const section = createSection()
|
|
62
|
-
;(section as unknown as { children: unknown[] }).children = [container]
|
|
63
|
-
return section as unknown as Record<string, unknown>
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function SavedSections({ onNavigate, config }: SavedSectionsProps) {
|
|
67
|
-
const [sections, setSections] = useState<SavedSection[]>([])
|
|
68
|
-
const [loading, setLoading] = useState(true)
|
|
69
|
-
const [error, setError] = useState<string | null>(null)
|
|
70
|
-
const [viewState, setViewState] = useState<ViewState>({ mode: 'list' })
|
|
71
|
-
const [activeCategory, setActiveCategory] = useState<Category | 'all'>('all')
|
|
72
|
-
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null)
|
|
73
|
-
const [saving, setSaving] = useState(false)
|
|
74
|
-
|
|
75
|
-
const fetchSections = useCallback(async () => {
|
|
76
|
-
setLoading(true)
|
|
77
|
-
setError(null)
|
|
78
|
-
const res = await cmsApi<SavedSection[]>('/saved-sections')
|
|
79
|
-
if (res.error) {
|
|
80
|
-
setError(res.error)
|
|
81
|
-
} else {
|
|
82
|
-
setSections(res.data ?? [])
|
|
83
|
-
}
|
|
84
|
-
setLoading(false)
|
|
85
|
-
}, [])
|
|
86
|
-
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
fetchSections()
|
|
89
|
-
}, [fetchSections])
|
|
90
|
-
|
|
91
|
-
const filteredSections =
|
|
92
|
-
activeCategory === 'all' ? sections : sections.filter((s) => s.category === activeCategory)
|
|
93
|
-
|
|
94
|
-
async function handleDelete(id: string) {
|
|
95
|
-
const res = await cmsApi(`/saved-sections/${id}`, { method: 'DELETE' })
|
|
96
|
-
if (res.error) {
|
|
97
|
-
toast.error(res.error)
|
|
98
|
-
} else {
|
|
99
|
-
toast.success('Section deleted')
|
|
100
|
-
setSections((prev) => prev.filter((s) => s.id !== id))
|
|
101
|
-
}
|
|
102
|
-
setDeleteConfirmId(null)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async function handleDuplicate(section: SavedSection) {
|
|
106
|
-
const res = await cmsApi<SavedSection>('/saved-sections', {
|
|
107
|
-
method: 'POST',
|
|
108
|
-
body: JSON.stringify({
|
|
109
|
-
name: `${section.name} (Copy)`,
|
|
110
|
-
description: section.description,
|
|
111
|
-
category: section.category,
|
|
112
|
-
tree: section.tree,
|
|
113
|
-
}),
|
|
114
|
-
})
|
|
115
|
-
if (res.error) {
|
|
116
|
-
toast.error(res.error)
|
|
117
|
-
} else {
|
|
118
|
-
toast.success('Section duplicated')
|
|
119
|
-
fetchSections()
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (viewState.mode === 'create' || viewState.mode === 'edit') {
|
|
124
|
-
return (
|
|
125
|
-
<SectionForm
|
|
126
|
-
initial={viewState.mode === 'edit' ? viewState.section : undefined}
|
|
127
|
-
saving={saving}
|
|
128
|
-
onSave={async (data) => {
|
|
129
|
-
setSaving(true)
|
|
130
|
-
if (viewState.mode === 'create') {
|
|
131
|
-
const res = await cmsApi<SavedSection>('/saved-sections', {
|
|
132
|
-
method: 'POST',
|
|
133
|
-
body: JSON.stringify({ ...data, tree: createEmptySectionTree() }),
|
|
134
|
-
})
|
|
135
|
-
setSaving(false)
|
|
136
|
-
if (res.error) {
|
|
137
|
-
toast.error(res.error)
|
|
138
|
-
} else {
|
|
139
|
-
toast.success('Section created')
|
|
140
|
-
setViewState({ mode: 'list' })
|
|
141
|
-
fetchSections()
|
|
142
|
-
}
|
|
143
|
-
} else {
|
|
144
|
-
const res = await cmsApi<SavedSection>(`/saved-sections/${viewState.section.id}`, {
|
|
145
|
-
method: 'PUT',
|
|
146
|
-
body: JSON.stringify(data),
|
|
147
|
-
})
|
|
148
|
-
setSaving(false)
|
|
149
|
-
if (res.error) {
|
|
150
|
-
toast.error(res.error)
|
|
151
|
-
} else {
|
|
152
|
-
toast.success('Section updated')
|
|
153
|
-
setViewState({ mode: 'list' })
|
|
154
|
-
fetchSections()
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}}
|
|
158
|
-
onCancel={() => setViewState({ mode: 'list' })}
|
|
159
|
-
/>
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return (
|
|
164
|
-
<div className="p-4 pr-8">
|
|
165
|
-
<div className="mb-6 flex items-center justify-between">
|
|
166
|
-
<div>
|
|
167
|
-
<h1 className="text-foreground">Saved Sections</h1>
|
|
168
|
-
<p className="text-muted-foreground mt-1 text-sm">
|
|
169
|
-
Reusable sections for the page builder
|
|
170
|
-
</p>
|
|
171
|
-
</div>
|
|
172
|
-
<button
|
|
173
|
-
type="button"
|
|
174
|
-
onClick={() => setViewState({ mode: 'create' })}
|
|
175
|
-
className="bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors"
|
|
176
|
-
>
|
|
177
|
-
<Plus size={16} />
|
|
178
|
-
Create Section
|
|
179
|
-
</button>
|
|
180
|
-
</div>
|
|
181
|
-
|
|
182
|
-
<div className="border-border mb-4 flex items-center gap-1 border-b">
|
|
183
|
-
<CategoryTab
|
|
184
|
-
label="All"
|
|
185
|
-
active={activeCategory === 'all'}
|
|
186
|
-
onClick={() => setActiveCategory('all')}
|
|
187
|
-
/>
|
|
188
|
-
{CATEGORIES.map((cat) => (
|
|
189
|
-
<CategoryTab
|
|
190
|
-
key={cat}
|
|
191
|
-
label={cat.charAt(0).toUpperCase() + cat.slice(1)}
|
|
192
|
-
active={activeCategory === cat}
|
|
193
|
-
onClick={() => setActiveCategory(cat)}
|
|
194
|
-
/>
|
|
195
|
-
))}
|
|
196
|
-
</div>
|
|
197
|
-
|
|
198
|
-
{loading && (
|
|
199
|
-
<div className="flex items-center justify-center py-16">
|
|
200
|
-
<Loader2 className="text-muted-foreground h-6 w-6 animate-spin" />
|
|
201
|
-
</div>
|
|
202
|
-
)}
|
|
203
|
-
|
|
204
|
-
{error && !loading && (
|
|
205
|
-
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
206
|
-
<AlertTriangle size={24} className="text-destructive mb-2" />
|
|
207
|
-
<p className="text-muted-foreground mb-3 text-sm">{error}</p>
|
|
208
|
-
<button
|
|
209
|
-
type="button"
|
|
210
|
-
onClick={fetchSections}
|
|
211
|
-
className="text-foreground border-border hover:bg-accent rounded-md border px-3 py-1.5 text-sm font-medium transition-colors"
|
|
212
|
-
>
|
|
213
|
-
Retry
|
|
214
|
-
</button>
|
|
215
|
-
</div>
|
|
216
|
-
)}
|
|
217
|
-
|
|
218
|
-
{!loading && !error && filteredSections.length === 0 && (
|
|
219
|
-
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
220
|
-
<Layers size={32} className="text-muted-foreground mb-3" />
|
|
221
|
-
<p className="text-foreground mb-1 text-sm font-medium">No saved sections</p>
|
|
222
|
-
<p className="text-muted-foreground mb-4 text-xs">
|
|
223
|
-
{activeCategory === 'all'
|
|
224
|
-
? 'Create your first reusable section to get started.'
|
|
225
|
-
: `No sections in the "${activeCategory}" category.`}
|
|
226
|
-
</p>
|
|
227
|
-
{activeCategory === 'all' && (
|
|
228
|
-
<button
|
|
229
|
-
type="button"
|
|
230
|
-
onClick={() => setViewState({ mode: 'create' })}
|
|
231
|
-
className="bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium transition-colors"
|
|
232
|
-
>
|
|
233
|
-
<Plus size={14} />
|
|
234
|
-
Create Section
|
|
235
|
-
</button>
|
|
236
|
-
)}
|
|
237
|
-
</div>
|
|
238
|
-
)}
|
|
239
|
-
|
|
240
|
-
{!loading && !error && filteredSections.length > 0 && (
|
|
241
|
-
<div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
|
|
242
|
-
{filteredSections.map((section) => (
|
|
243
|
-
<div
|
|
244
|
-
key={section.id}
|
|
245
|
-
className="border-border bg-card hover:border-primary/50 rounded-lg border p-4 transition-colors"
|
|
246
|
-
>
|
|
247
|
-
<div className="flex items-start justify-between gap-3">
|
|
248
|
-
<div className="min-w-0 flex-1">
|
|
249
|
-
<p className="text-foreground truncate text-sm font-medium">{section.name}</p>
|
|
250
|
-
{section.description && (
|
|
251
|
-
<p className="text-muted-foreground mt-0.5 line-clamp-2 text-xs">
|
|
252
|
-
{section.description}
|
|
253
|
-
</p>
|
|
254
|
-
)}
|
|
255
|
-
</div>
|
|
256
|
-
<span
|
|
257
|
-
className={`shrink-0 rounded-full px-2 py-0.5 text-xs ${CATEGORY_COLORS[section.category] ?? 'bg-muted text-muted-foreground'}`}
|
|
258
|
-
>
|
|
259
|
-
{section.category}
|
|
260
|
-
</span>
|
|
261
|
-
</div>
|
|
262
|
-
|
|
263
|
-
<div className="text-muted-foreground mt-3 flex items-center gap-4 text-xs">
|
|
264
|
-
<span className="flex items-center gap-1">
|
|
265
|
-
<Hash size={12} />
|
|
266
|
-
{section.usageCount} use{section.usageCount !== 1 ? 's' : ''}
|
|
267
|
-
</span>
|
|
268
|
-
<span className="flex items-center gap-1">
|
|
269
|
-
<Calendar size={12} />
|
|
270
|
-
{formatDate(section.createdAt)}
|
|
271
|
-
</span>
|
|
272
|
-
{section.tree && (
|
|
273
|
-
<span className="flex items-center gap-1">
|
|
274
|
-
<Layers size={12} />
|
|
275
|
-
{JSON.stringify(section.tree).length > 1000 ? 'Complex' : 'Simple'}
|
|
276
|
-
</span>
|
|
277
|
-
)}
|
|
278
|
-
</div>
|
|
279
|
-
|
|
280
|
-
<div className="border-border mt-3 flex items-center gap-1 border-t pt-3">
|
|
281
|
-
<button
|
|
282
|
-
type="button"
|
|
283
|
-
onClick={() => setViewState({ mode: 'edit', section })}
|
|
284
|
-
className="text-foreground hover:bg-accent flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors"
|
|
285
|
-
aria-label={`Edit ${section.name}`}
|
|
286
|
-
>
|
|
287
|
-
<Edit size={13} />
|
|
288
|
-
Edit
|
|
289
|
-
</button>
|
|
290
|
-
<button
|
|
291
|
-
type="button"
|
|
292
|
-
onClick={() => handleDuplicate(section)}
|
|
293
|
-
className="text-foreground hover:bg-accent flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors"
|
|
294
|
-
aria-label={`Duplicate ${section.name}`}
|
|
295
|
-
>
|
|
296
|
-
<Copy size={13} />
|
|
297
|
-
Duplicate
|
|
298
|
-
</button>
|
|
299
|
-
{deleteConfirmId === section.id ? (
|
|
300
|
-
<div className="ml-auto flex items-center gap-1">
|
|
301
|
-
<span className="text-destructive mr-1 text-xs">Delete?</span>
|
|
302
|
-
<button
|
|
303
|
-
type="button"
|
|
304
|
-
onClick={() => handleDelete(section.id)}
|
|
305
|
-
className="text-destructive-foreground bg-destructive hover:bg-destructive/90 rounded-md px-2 py-1 text-xs font-medium transition-colors"
|
|
306
|
-
>
|
|
307
|
-
Yes
|
|
308
|
-
</button>
|
|
309
|
-
<button
|
|
310
|
-
type="button"
|
|
311
|
-
onClick={() => setDeleteConfirmId(null)}
|
|
312
|
-
className="text-foreground border-border hover:bg-accent rounded-md border px-2 py-1 text-xs font-medium transition-colors"
|
|
313
|
-
>
|
|
314
|
-
No
|
|
315
|
-
</button>
|
|
316
|
-
</div>
|
|
317
|
-
) : (
|
|
318
|
-
<button
|
|
319
|
-
type="button"
|
|
320
|
-
onClick={() => setDeleteConfirmId(section.id)}
|
|
321
|
-
className="text-destructive hover:bg-destructive/10 ml-auto flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors"
|
|
322
|
-
aria-label={`Delete ${section.name}`}
|
|
323
|
-
>
|
|
324
|
-
<Trash2 size={13} />
|
|
325
|
-
Delete
|
|
326
|
-
</button>
|
|
327
|
-
)}
|
|
328
|
-
</div>
|
|
329
|
-
</div>
|
|
330
|
-
))}
|
|
331
|
-
</div>
|
|
332
|
-
)}
|
|
333
|
-
</div>
|
|
334
|
-
)
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
interface CategoryTabProps {
|
|
338
|
-
label: string
|
|
339
|
-
active: boolean
|
|
340
|
-
onClick: () => void
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function CategoryTab({ label, active, onClick }: CategoryTabProps) {
|
|
344
|
-
return (
|
|
345
|
-
<button
|
|
346
|
-
type="button"
|
|
347
|
-
onClick={onClick}
|
|
348
|
-
className={`-mb-px border-b-2 px-3 py-2 text-sm transition-colors ${
|
|
349
|
-
active
|
|
350
|
-
? 'border-primary text-foreground font-medium'
|
|
351
|
-
: 'text-muted-foreground hover:text-foreground border-transparent'
|
|
352
|
-
}`}
|
|
353
|
-
>
|
|
354
|
-
{label}
|
|
355
|
-
</button>
|
|
356
|
-
)
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
interface SectionFormProps {
|
|
360
|
-
initial?: SavedSection
|
|
361
|
-
saving: boolean
|
|
362
|
-
onSave: (data: { name: string; description: string; category: string }) => void
|
|
363
|
-
onCancel: () => void
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function SectionForm({ initial, saving, onSave, onCancel }: SectionFormProps) {
|
|
367
|
-
const [name, setName] = useState(initial?.name ?? '')
|
|
368
|
-
const [description, setDescription] = useState(initial?.description ?? '')
|
|
369
|
-
const [category, setCategory] = useState(initial?.category ?? 'content')
|
|
370
|
-
|
|
371
|
-
function handleSubmit(e: React.FormEvent) {
|
|
372
|
-
e.preventDefault()
|
|
373
|
-
if (!name.trim()) return
|
|
374
|
-
onSave({ name: name.trim(), description: description.trim(), category })
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const treeSize = initial?.tree ? JSON.stringify(initial.tree).length : null
|
|
378
|
-
|
|
379
|
-
return (
|
|
380
|
-
<div className="p-4 pr-8">
|
|
381
|
-
<div className="mb-6">
|
|
382
|
-
<button
|
|
383
|
-
type="button"
|
|
384
|
-
onClick={onCancel}
|
|
385
|
-
className="text-muted-foreground hover:text-foreground mb-3 flex items-center gap-1.5 text-sm transition-colors"
|
|
386
|
-
aria-label="Back to saved sections"
|
|
387
|
-
>
|
|
388
|
-
<ArrowLeft size={16} />
|
|
389
|
-
Back
|
|
390
|
-
</button>
|
|
391
|
-
<h1 className="text-foreground">
|
|
392
|
-
{initial ? `Edit: ${initial.name}` : 'Create Saved Section'}
|
|
393
|
-
</h1>
|
|
394
|
-
</div>
|
|
395
|
-
|
|
396
|
-
<form onSubmit={handleSubmit} className="max-w-lg space-y-4">
|
|
397
|
-
<div>
|
|
398
|
-
<label htmlFor="section-name" className="text-foreground mb-1 block text-sm font-medium">
|
|
399
|
-
Name <span className="text-destructive">*</span>
|
|
400
|
-
</label>
|
|
401
|
-
<input
|
|
402
|
-
id="section-name"
|
|
403
|
-
type="text"
|
|
404
|
-
value={name}
|
|
405
|
-
onChange={(e) => setName(e.target.value)}
|
|
406
|
-
required
|
|
407
|
-
placeholder="e.g. Hero Banner"
|
|
408
|
-
className="bg-background border-input focus:ring-ring w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
|
|
409
|
-
/>
|
|
410
|
-
</div>
|
|
411
|
-
|
|
412
|
-
<div>
|
|
413
|
-
<label
|
|
414
|
-
htmlFor="section-description"
|
|
415
|
-
className="text-foreground mb-1 block text-sm font-medium"
|
|
416
|
-
>
|
|
417
|
-
Description
|
|
418
|
-
</label>
|
|
419
|
-
<textarea
|
|
420
|
-
id="section-description"
|
|
421
|
-
value={description}
|
|
422
|
-
onChange={(e) => setDescription(e.target.value)}
|
|
423
|
-
placeholder="Optional description of this section..."
|
|
424
|
-
rows={3}
|
|
425
|
-
className="bg-background border-input focus:ring-ring w-full resize-none rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
|
|
426
|
-
/>
|
|
427
|
-
</div>
|
|
428
|
-
|
|
429
|
-
<div>
|
|
430
|
-
<label
|
|
431
|
-
htmlFor="section-category"
|
|
432
|
-
className="text-foreground mb-1 block text-sm font-medium"
|
|
433
|
-
>
|
|
434
|
-
Category
|
|
435
|
-
</label>
|
|
436
|
-
<select
|
|
437
|
-
id="section-category"
|
|
438
|
-
value={category}
|
|
439
|
-
onChange={(e) => setCategory(e.target.value)}
|
|
440
|
-
className="bg-background border-input focus:ring-ring w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
|
|
441
|
-
>
|
|
442
|
-
{CATEGORIES.map((cat) => (
|
|
443
|
-
<option key={cat} value={cat}>
|
|
444
|
-
{cat.charAt(0).toUpperCase() + cat.slice(1)}
|
|
445
|
-
</option>
|
|
446
|
-
))}
|
|
447
|
-
</select>
|
|
448
|
-
</div>
|
|
449
|
-
|
|
450
|
-
{treeSize !== null && (
|
|
451
|
-
<div className="border-border bg-muted rounded-md border p-3">
|
|
452
|
-
<p className="text-muted-foreground text-xs">
|
|
453
|
-
Section tree editing is available in the page builder. Current tree size:{' '}
|
|
454
|
-
{(treeSize / 1024).toFixed(1)} KB
|
|
455
|
-
</p>
|
|
456
|
-
</div>
|
|
457
|
-
)}
|
|
458
|
-
|
|
459
|
-
<div className="flex items-center gap-3 pt-2">
|
|
460
|
-
<button
|
|
461
|
-
type="submit"
|
|
462
|
-
disabled={saving || !name.trim()}
|
|
463
|
-
className="bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"
|
|
464
|
-
>
|
|
465
|
-
{saving && <Loader2 size={14} className="animate-spin" />}
|
|
466
|
-
{initial ? 'Save Changes' : 'Create Section'}
|
|
467
|
-
</button>
|
|
468
|
-
<button
|
|
469
|
-
type="button"
|
|
470
|
-
onClick={onCancel}
|
|
471
|
-
disabled={saving}
|
|
472
|
-
className="text-foreground border-border hover:bg-accent rounded-lg border px-4 py-2 text-sm font-medium transition-colors disabled:opacity-50"
|
|
473
|
-
>
|
|
474
|
-
Cancel
|
|
475
|
-
</button>
|
|
476
|
-
</div>
|
|
477
|
-
</form>
|
|
478
|
-
</div>
|
|
479
|
-
)
|
|
480
|
-
}
|
|
@@ -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
|
-
}
|