@actuate-media/cms-admin 0.7.3 → 0.8.1
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/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +95 -42
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/lib/search.test.js +10 -10
- package/dist/__tests__/lib/search.test.js.map +1 -1
- package/dist/__tests__/lib/utils.test.js.map +1 -1
- package/dist/__tests__/router/match-route.test.js.map +1 -1
- package/dist/__tests__/router/strip-base.test.js.map +1 -1
- package/dist/actuate-admin.css +1 -1
- package/dist/components/Breadcrumbs.d.ts.map +1 -1
- package/dist/components/Breadcrumbs.js +2 -4
- package/dist/components/Breadcrumbs.js.map +1 -1
- package/dist/components/CommandPalette.d.ts.map +1 -1
- package/dist/components/CommandPalette.js +7 -3
- package/dist/components/CommandPalette.js.map +1 -1
- package/dist/components/ContentOverviewChart.d.ts.map +1 -1
- package/dist/components/ContentOverviewChart.js.map +1 -1
- package/dist/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/ErrorBoundary.js.map +1 -1
- package/dist/components/FocalPointPicker.d.ts.map +1 -1
- package/dist/components/FocalPointPicker.js +4 -2
- package/dist/components/FocalPointPicker.js.map +1 -1
- package/dist/components/FolderTree.d.ts.map +1 -1
- package/dist/components/FolderTree.js +18 -10
- package/dist/components/FolderTree.js.map +1 -1
- package/dist/components/LivePreview.d.ts +1 -1
- package/dist/components/LivePreview.d.ts.map +1 -1
- package/dist/components/LivePreview.js +6 -2
- package/dist/components/LivePreview.js.map +1 -1
- package/dist/components/LocaleProvider.d.ts.map +1 -1
- package/dist/components/LocaleProvider.js.map +1 -1
- package/dist/components/LocaleSwitcher.d.ts.map +1 -1
- package/dist/components/LocaleSwitcher.js +1 -1
- package/dist/components/LocaleSwitcher.js.map +1 -1
- package/dist/components/MediaPickerModal.d.ts.map +1 -1
- package/dist/components/MediaPickerModal.js.map +1 -1
- package/dist/components/PresenceIndicator.d.ts.map +1 -1
- package/dist/components/PresenceIndicator.js +5 -2
- package/dist/components/PresenceIndicator.js.map +1 -1
- package/dist/components/SEOPanel.d.ts +1 -1
- package/dist/components/SEOPanel.d.ts.map +1 -1
- package/dist/components/SEOPanel.js +110 -24
- package/dist/components/SEOPanel.js.map +1 -1
- package/dist/components/SEOPerformance.d.ts.map +1 -1
- package/dist/components/SEOPerformance.js +2 -2
- package/dist/components/SEOPerformance.js.map +1 -1
- package/dist/components/ThemeProvider.d.ts.map +1 -1
- package/dist/components/ThemeProvider.js.map +1 -1
- package/dist/components/TipTapEditor.d.ts.map +1 -1
- package/dist/components/TipTapEditor.js +5 -1
- package/dist/components/TipTapEditor.js.map +1 -1
- package/dist/components/VersionHistory.d.ts +1 -1
- package/dist/components/VersionHistory.d.ts.map +1 -1
- package/dist/components/VersionHistory.js +1 -1
- package/dist/components/VersionHistory.js.map +1 -1
- package/dist/components/ui/Avatar.d.ts.map +1 -1
- package/dist/components/ui/Avatar.js.map +1 -1
- package/dist/components/ui/Badge.d.ts.map +1 -1
- package/dist/components/ui/Badge.js.map +1 -1
- package/dist/components/ui/Button.d.ts.map +1 -1
- package/dist/components/ui/Button.js.map +1 -1
- package/dist/components/ui/CommandPalette.d.ts.map +1 -1
- package/dist/components/ui/CommandPalette.js +8 -2
- package/dist/components/ui/CommandPalette.js.map +1 -1
- package/dist/components/ui/ConfirmDialog.d.ts.map +1 -1
- package/dist/components/ui/ConfirmDialog.js.map +1 -1
- package/dist/components/ui/DataTable.d.ts.map +1 -1
- package/dist/components/ui/DataTable.js +1 -3
- package/dist/components/ui/DataTable.js.map +1 -1
- package/dist/components/ui/EmptyState.d.ts.map +1 -1
- package/dist/components/ui/EmptyState.js +1 -1
- package/dist/components/ui/EmptyState.js.map +1 -1
- package/dist/components/ui/Modal.d.ts.map +1 -1
- package/dist/components/ui/Modal.js.map +1 -1
- package/dist/components/ui/Pagination.d.ts +1 -1
- package/dist/components/ui/Pagination.d.ts.map +1 -1
- package/dist/components/ui/Pagination.js +7 -2
- package/dist/components/ui/Pagination.js.map +1 -1
- package/dist/components/ui/SearchInput.d.ts.map +1 -1
- package/dist/components/ui/SearchInput.js.map +1 -1
- package/dist/components/ui/Skeleton.d.ts.map +1 -1
- package/dist/components/ui/Skeleton.js.map +1 -1
- package/dist/components/ui/Toast.d.ts.map +1 -1
- package/dist/components/ui/Toast.js.map +1 -1
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js.map +1 -1
- package/dist/fields/ArrayField.d.ts.map +1 -1
- package/dist/fields/ArrayField.js +1 -1
- package/dist/fields/ArrayField.js.map +1 -1
- package/dist/fields/BlockBuilderField.d.ts.map +1 -1
- package/dist/fields/BlockBuilderField.js +7 -7
- package/dist/fields/BlockBuilderField.js.map +1 -1
- package/dist/fields/DateField.d.ts.map +1 -1
- package/dist/fields/DateField.js +1 -1
- package/dist/fields/DateField.js.map +1 -1
- package/dist/fields/FieldRenderer.d.ts.map +1 -1
- package/dist/fields/FieldRenderer.js.map +1 -1
- package/dist/fields/GroupField.d.ts.map +1 -1
- package/dist/fields/GroupField.js +1 -1
- package/dist/fields/GroupField.js.map +1 -1
- package/dist/fields/MediaField.d.ts.map +1 -1
- package/dist/fields/MediaField.js +1 -1
- package/dist/fields/MediaField.js.map +1 -1
- package/dist/fields/NavBuilderField.d.ts.map +1 -1
- package/dist/fields/NavBuilderField.js +2 -5
- package/dist/fields/NavBuilderField.js.map +1 -1
- package/dist/fields/NumberField.d.ts +1 -1
- package/dist/fields/NumberField.d.ts.map +1 -1
- package/dist/fields/NumberField.js +2 -2
- package/dist/fields/NumberField.js.map +1 -1
- package/dist/fields/RelationshipField.d.ts.map +1 -1
- package/dist/fields/RelationshipField.js +7 -3
- package/dist/fields/RelationshipField.js.map +1 -1
- package/dist/fields/RichTextField.d.ts +1 -1
- package/dist/fields/RichTextField.d.ts.map +1 -1
- package/dist/fields/RichTextField.js +2 -2
- package/dist/fields/RichTextField.js.map +1 -1
- package/dist/fields/SelectField.d.ts.map +1 -1
- package/dist/fields/SelectField.js +9 -7
- package/dist/fields/SelectField.js.map +1 -1
- package/dist/fields/SlugField.d.ts.map +1 -1
- package/dist/fields/SlugField.js +1 -1
- package/dist/fields/SlugField.js.map +1 -1
- package/dist/fields/TextField.d.ts +1 -1
- package/dist/fields/TextField.d.ts.map +1 -1
- package/dist/fields/TextField.js +2 -2
- package/dist/fields/TextField.js.map +1 -1
- package/dist/fields/ToggleField.d.ts.map +1 -1
- package/dist/fields/ToggleField.js +1 -1
- package/dist/fields/ToggleField.js.map +1 -1
- package/dist/fields/block-types.d.ts.map +1 -1
- package/dist/fields/block-types.js +28 -8
- package/dist/fields/block-types.js.map +1 -1
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js.map +1 -1
- package/dist/hooks/useBuilderState.d.ts.map +1 -1
- package/dist/hooks/useBuilderState.js.map +1 -1
- package/dist/hooks/useContentLock.d.ts.map +1 -1
- package/dist/hooks/useContentLock.js.map +1 -1
- package/dist/hooks/useDebounce.js.map +1 -1
- package/dist/hooks/useKeyboardShortcuts.d.ts.map +1 -1
- package/dist/hooks/useKeyboardShortcuts.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/layout/Header.d.ts.map +1 -1
- package/dist/layout/Header.js.map +1 -1
- package/dist/layout/Layout.d.ts.map +1 -1
- package/dist/layout/Layout.js.map +1 -1
- package/dist/layout/Sidebar.d.ts +1 -1
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +5 -8
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +33 -4
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/search.js +3 -5
- package/dist/lib/search.js.map +1 -1
- package/dist/lib/useApiData.d.ts.map +1 -1
- package/dist/lib/useApiData.js.map +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +1 -3
- package/dist/router/index.js.map +1 -1
- package/dist/views/CollectionList.d.ts.map +1 -1
- package/dist/views/CollectionList.js +56 -17
- package/dist/views/CollectionList.js.map +1 -1
- package/dist/views/Dashboard.d.ts.map +1 -1
- package/dist/views/Dashboard.js +26 -13
- package/dist/views/Dashboard.js.map +1 -1
- package/dist/views/DocumentEdit.d.ts +1 -1
- package/dist/views/DocumentEdit.d.ts.map +1 -1
- package/dist/views/DocumentEdit.js +33 -15
- package/dist/views/DocumentEdit.js.map +1 -1
- package/dist/views/ForgotPassword.d.ts.map +1 -1
- package/dist/views/ForgotPassword.js.map +1 -1
- package/dist/views/FormEditor.d.ts.map +1 -1
- package/dist/views/FormEditor.js +8 -2
- package/dist/views/FormEditor.js.map +1 -1
- package/dist/views/FormSubmissions.d.ts.map +1 -1
- package/dist/views/FormSubmissions.js +6 -6
- package/dist/views/FormSubmissions.js.map +1 -1
- package/dist/views/Forms.d.ts.map +1 -1
- package/dist/views/Forms.js.map +1 -1
- package/dist/views/Login.d.ts.map +1 -1
- package/dist/views/Login.js +5 -2
- package/dist/views/Login.js.map +1 -1
- package/dist/views/MediaBrowser.d.ts.map +1 -1
- package/dist/views/MediaBrowser.js +39 -19
- package/dist/views/MediaBrowser.js.map +1 -1
- package/dist/views/PageEditor.d.ts.map +1 -1
- package/dist/views/PageEditor.js.map +1 -1
- package/dist/views/Pages.d.ts.map +1 -1
- package/dist/views/Pages.js +20 -10
- package/dist/views/Pages.js.map +1 -1
- package/dist/views/PostEditor.d.ts.map +1 -1
- package/dist/views/PostEditor.js.map +1 -1
- package/dist/views/Posts.d.ts.map +1 -1
- package/dist/views/Posts.js +13 -7
- package/dist/views/Posts.js.map +1 -1
- package/dist/views/Redirects.d.ts.map +1 -1
- package/dist/views/Redirects.js +17 -5
- package/dist/views/Redirects.js.map +1 -1
- package/dist/views/ResetPassword.d.ts.map +1 -1
- package/dist/views/ResetPassword.js.map +1 -1
- package/dist/views/SEO.d.ts.map +1 -1
- package/dist/views/SEO.js +39 -16
- package/dist/views/SEO.js.map +1 -1
- package/dist/views/ScriptTagEditor.d.ts.map +1 -1
- package/dist/views/ScriptTagEditor.js +2 -1
- package/dist/views/ScriptTagEditor.js.map +1 -1
- package/dist/views/ScriptTags.d.ts.map +1 -1
- package/dist/views/ScriptTags.js.map +1 -1
- package/dist/views/Settings.d.ts.map +1 -1
- package/dist/views/Settings.js +38 -11
- package/dist/views/Settings.js.map +1 -1
- package/dist/views/SetupWizard.d.ts.map +1 -1
- package/dist/views/SetupWizard.js.map +1 -1
- package/dist/views/Users.d.ts.map +1 -1
- package/dist/views/Users.js +5 -3
- package/dist/views/Users.js.map +1 -1
- package/dist/views/page-builder/AIBlockAssist.d.ts.map +1 -1
- package/dist/views/page-builder/AIBlockAssist.js +1 -1
- package/dist/views/page-builder/AIBlockAssist.js.map +1 -1
- package/dist/views/page-builder/AIGenerateDialog.d.ts.map +1 -1
- package/dist/views/page-builder/AIGenerateDialog.js +4 -1
- package/dist/views/page-builder/AIGenerateDialog.js.map +1 -1
- package/dist/views/page-builder/BlockEditor.d.ts.map +1 -1
- package/dist/views/page-builder/BlockEditor.js +94 -3
- package/dist/views/page-builder/BlockEditor.js.map +1 -1
- package/dist/views/page-builder/BlockPicker.d.ts.map +1 -1
- package/dist/views/page-builder/BlockPicker.js.map +1 -1
- package/dist/views/page-builder/BottomBar.d.ts.map +1 -1
- package/dist/views/page-builder/BottomBar.js.map +1 -1
- package/dist/views/page-builder/BuilderToolbar.d.ts.map +1 -1
- package/dist/views/page-builder/BuilderToolbar.js.map +1 -1
- package/dist/views/page-builder/ContextPanel.d.ts.map +1 -1
- package/dist/views/page-builder/ContextPanel.js +4 -1
- package/dist/views/page-builder/ContextPanel.js.map +1 -1
- package/dist/views/page-builder/DesignScore.d.ts.map +1 -1
- package/dist/views/page-builder/DesignScore.js.map +1 -1
- package/dist/views/page-builder/NodeSettings.d.ts.map +1 -1
- package/dist/views/page-builder/NodeSettings.js +1 -1
- package/dist/views/page-builder/NodeSettings.js.map +1 -1
- package/dist/views/page-builder/PageBuilder.d.ts +1 -1
- package/dist/views/page-builder/PageBuilder.d.ts.map +1 -1
- package/dist/views/page-builder/PageBuilder.js +25 -3
- package/dist/views/page-builder/PageBuilder.js.map +1 -1
- package/dist/views/page-builder/PageSettings.d.ts.map +1 -1
- package/dist/views/page-builder/PageSettings.js.map +1 -1
- package/dist/views/page-builder/PageTemplates.d.ts.map +1 -1
- package/dist/views/page-builder/PageTemplates.js.map +1 -1
- package/dist/views/page-builder/SEOPanel.d.ts.map +1 -1
- package/dist/views/page-builder/SEOPanel.js +1 -3
- package/dist/views/page-builder/SEOPanel.js.map +1 -1
- package/dist/views/page-builder/SavedSections.d.ts.map +1 -1
- package/dist/views/page-builder/SavedSections.js +3 -7
- package/dist/views/page-builder/SavedSections.js.map +1 -1
- package/dist/views/page-builder/TemplatePicker.d.ts.map +1 -1
- package/dist/views/page-builder/TemplatePicker.js.map +1 -1
- package/dist/views/page-builder/block-renderers/CTAPreview.d.ts.map +1 -1
- package/dist/views/page-builder/block-renderers/CTAPreview.js +1 -1
- package/dist/views/page-builder/block-renderers/CTAPreview.js.map +1 -1
- package/dist/views/page-builder/block-renderers/CardsPreview.d.ts.map +1 -1
- package/dist/views/page-builder/block-renderers/CardsPreview.js +1 -1
- package/dist/views/page-builder/block-renderers/CardsPreview.js.map +1 -1
- package/dist/views/page-builder/block-renderers/CodePreview.d.ts.map +1 -1
- package/dist/views/page-builder/block-renderers/CodePreview.js +1 -5
- package/dist/views/page-builder/block-renderers/CodePreview.js.map +1 -1
- package/dist/views/page-builder/block-renderers/FAQPreview.d.ts.map +1 -1
- package/dist/views/page-builder/block-renderers/FAQPreview.js +4 -1
- package/dist/views/page-builder/block-renderers/FAQPreview.js.map +1 -1
- package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts.map +1 -1
- package/dist/views/page-builder/block-renderers/FallbackPreview.js.map +1 -1
- package/dist/views/page-builder/block-renderers/FormPreview.d.ts.map +1 -1
- package/dist/views/page-builder/block-renderers/FormPreview.js +2 -2
- package/dist/views/page-builder/block-renderers/FormPreview.js.map +1 -1
- package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts.map +1 -1
- package/dist/views/page-builder/block-renderers/GalleryPreview.js +1 -3
- package/dist/views/page-builder/block-renderers/GalleryPreview.js.map +1 -1
- package/dist/views/page-builder/block-renderers/HeroPreview.d.ts.map +1 -1
- package/dist/views/page-builder/block-renderers/HeroPreview.js.map +1 -1
- package/dist/views/page-builder/block-renderers/ImagePreview.d.ts.map +1 -1
- package/dist/views/page-builder/block-renderers/ImagePreview.js.map +1 -1
- package/dist/views/page-builder/block-renderers/TextPreview.d.ts.map +1 -1
- package/dist/views/page-builder/block-renderers/TextPreview.js +2 -6
- package/dist/views/page-builder/block-renderers/TextPreview.js.map +1 -1
- package/dist/views/page-builder/block-renderers/VideoPreview.d.ts.map +1 -1
- package/dist/views/page-builder/block-renderers/VideoPreview.js +2 -5
- package/dist/views/page-builder/block-renderers/VideoPreview.js.map +1 -1
- package/dist/views/page-builder/block-renderers/index.d.ts.map +1 -1
- package/dist/views/page-builder/block-renderers/index.js.map +1 -1
- package/dist/views/page-builder/canvas/BlockRenderer.d.ts.map +1 -1
- package/dist/views/page-builder/canvas/BlockRenderer.js +1 -5
- package/dist/views/page-builder/canvas/BlockRenderer.js.map +1 -1
- package/dist/views/page-builder/canvas/BuilderCanvas.d.ts.map +1 -1
- package/dist/views/page-builder/canvas/BuilderCanvas.js.map +1 -1
- package/dist/views/page-builder/canvas/ColumnRenderer.d.ts.map +1 -1
- package/dist/views/page-builder/canvas/ColumnRenderer.js +1 -5
- package/dist/views/page-builder/canvas/ColumnRenderer.js.map +1 -1
- package/dist/views/page-builder/canvas/ContainerRenderer.d.ts.map +1 -1
- package/dist/views/page-builder/canvas/ContainerRenderer.js +1 -5
- package/dist/views/page-builder/canvas/ContainerRenderer.js.map +1 -1
- package/dist/views/page-builder/canvas/RowRenderer.d.ts.map +1 -1
- package/dist/views/page-builder/canvas/RowRenderer.js +1 -5
- package/dist/views/page-builder/canvas/RowRenderer.js.map +1 -1
- package/dist/views/page-builder/canvas/SectionRenderer.d.ts.map +1 -1
- package/dist/views/page-builder/canvas/SectionRenderer.js +1 -5
- package/dist/views/page-builder/canvas/SectionRenderer.js.map +1 -1
- package/dist/views/page-builder/canvas/index.d.ts.map +1 -1
- package/dist/views/page-builder/canvas/index.js.map +1 -1
- package/package.json +2 -2
- package/src/AdminRoot.tsx +302 -177
- package/src/__tests__/lib/search.test.ts +60 -69
- package/src/__tests__/lib/utils.test.ts +12 -12
- package/src/__tests__/router/match-route.test.ts +24 -26
- package/src/__tests__/router/strip-base.test.ts +15 -15
- package/src/components/Breadcrumbs.tsx +27 -24
- package/src/components/CommandPalette.tsx +115 -99
- package/src/components/ContentOverviewChart.tsx +19 -14
- package/src/components/ErrorBoundary.tsx +13 -13
- package/src/components/FocalPointPicker.tsx +31 -20
- package/src/components/FolderTree.tsx +172 -139
- package/src/components/LivePreview.tsx +68 -41
- package/src/components/LocaleProvider.tsx +26 -20
- package/src/components/LocaleSwitcher.tsx +9 -11
- package/src/components/MediaPickerModal.tsx +46 -45
- package/src/components/PresenceIndicator.tsx +30 -27
- package/src/components/SEOPanel.tsx +378 -228
- package/src/components/SEOPerformance.tsx +52 -30
- package/src/components/ThemeProvider.tsx +46 -46
- package/src/components/TipTapEditor.tsx +60 -64
- package/src/components/VersionHistory.tsx +63 -52
- package/src/components/ui/Avatar.tsx +8 -8
- package/src/components/ui/Badge.tsx +7 -5
- package/src/components/ui/Button.tsx +24 -13
- package/src/components/ui/CommandPalette.tsx +56 -42
- package/src/components/ui/ConfirmDialog.tsx +14 -14
- package/src/components/ui/DataTable.tsx +37 -39
- package/src/components/ui/EmptyState.tsx +9 -11
- package/src/components/ui/Modal.tsx +21 -15
- package/src/components/ui/Pagination.tsx +34 -19
- package/src/components/ui/SearchInput.tsx +17 -7
- package/src/components/ui/Skeleton.tsx +7 -7
- package/src/components/ui/Toast.tsx +29 -22
- package/src/components/ui/index.ts +24 -24
- package/src/fields/ArrayField.tsx +43 -25
- package/src/fields/BlockBuilderField.tsx +80 -99
- package/src/fields/DateField.tsx +20 -12
- package/src/fields/FieldRenderer.tsx +34 -34
- package/src/fields/GroupField.tsx +8 -10
- package/src/fields/MediaField.tsx +8 -10
- package/src/fields/NavBuilderField.tsx +24 -25
- package/src/fields/NumberField.tsx +21 -14
- package/src/fields/RelationshipField.tsx +105 -91
- package/src/fields/RichTextField.tsx +16 -12
- package/src/fields/SelectField.tsx +42 -34
- package/src/fields/SlugField.tsx +29 -17
- package/src/fields/TextField.tsx +24 -16
- package/src/fields/ToggleField.tsx +7 -9
- package/src/fields/block-types.ts +50 -24
- package/src/fields/index.ts +17 -17
- package/src/hooks/useBuilderState.ts +260 -221
- package/src/hooks/useContentLock.ts +23 -20
- package/src/hooks/useDebounce.ts +7 -7
- package/src/hooks/useKeyboardShortcuts.ts +16 -16
- package/src/index.ts +69 -58
- package/src/layout/Header.tsx +21 -20
- package/src/layout/Layout.tsx +22 -24
- package/src/layout/Sidebar.tsx +107 -72
- package/src/lib/api.ts +58 -30
- package/src/lib/search.ts +30 -34
- package/src/lib/useApiData.ts +65 -62
- package/src/lib/utils.ts +3 -3
- package/src/router/index.ts +33 -35
- package/src/styles/build-input.css +2 -2
- package/src/styles/tailwind.css +1 -1
- package/src/styles/theme.css +7 -1
- package/src/views/CollectionList.tsx +275 -121
- package/src/views/Dashboard.tsx +164 -117
- package/src/views/DocumentEdit.tsx +298 -253
- package/src/views/ForgotPassword.tsx +27 -23
- package/src/views/FormEditor.tsx +165 -99
- package/src/views/FormSubmissions.tsx +261 -117
- package/src/views/Forms.tsx +56 -26
- package/src/views/Login.tsx +107 -84
- package/src/views/MediaBrowser.tsx +717 -523
- package/src/views/PageEditor.tsx +44 -46
- package/src/views/Pages.tsx +312 -149
- package/src/views/PostEditor.tsx +57 -51
- package/src/views/Posts.tsx +206 -74
- package/src/views/Redirects.tsx +173 -117
- package/src/views/ResetPassword.tsx +43 -32
- package/src/views/SEO.tsx +589 -160
- package/src/views/ScriptTagEditor.tsx +69 -69
- package/src/views/ScriptTags.tsx +54 -42
- package/src/views/Settings.tsx +430 -220
- package/src/views/SetupWizard.tsx +69 -46
- package/src/views/Users.tsx +154 -120
- package/src/views/page-builder/AIBlockAssist.tsx +21 -25
- package/src/views/page-builder/AIGenerateDialog.tsx +134 -127
- package/src/views/page-builder/BlockEditor.tsx +258 -81
- package/src/views/page-builder/BlockPicker.tsx +73 -88
- package/src/views/page-builder/BottomBar.tsx +15 -11
- package/src/views/page-builder/BuilderToolbar.tsx +32 -29
- package/src/views/page-builder/ContextPanel.tsx +57 -57
- package/src/views/page-builder/DesignScore.tsx +52 -59
- package/src/views/page-builder/NodeSettings.tsx +59 -59
- package/src/views/page-builder/PageBuilder.tsx +164 -146
- package/src/views/page-builder/PageSettings.tsx +16 -15
- package/src/views/page-builder/PageTemplates.tsx +23 -17
- package/src/views/page-builder/SEOPanel.tsx +90 -111
- package/src/views/page-builder/SavedSections.tsx +99 -105
- package/src/views/page-builder/TemplatePicker.tsx +44 -48
- package/src/views/page-builder/block-renderers/CTAPreview.tsx +11 -13
- package/src/views/page-builder/block-renderers/CardsPreview.tsx +13 -15
- package/src/views/page-builder/block-renderers/CodePreview.tsx +16 -16
- package/src/views/page-builder/block-renderers/FAQPreview.tsx +20 -23
- package/src/views/page-builder/block-renderers/FallbackPreview.tsx +5 -5
- package/src/views/page-builder/block-renderers/FormPreview.tsx +9 -13
- package/src/views/page-builder/block-renderers/GalleryPreview.tsx +22 -28
- package/src/views/page-builder/block-renderers/HeroPreview.tsx +17 -30
- package/src/views/page-builder/block-renderers/ImagePreview.tsx +12 -12
- package/src/views/page-builder/block-renderers/TextPreview.tsx +22 -22
- package/src/views/page-builder/block-renderers/VideoPreview.tsx +13 -18
- package/src/views/page-builder/block-renderers/index.ts +17 -17
- package/src/views/page-builder/canvas/BlockRenderer.tsx +19 -23
- package/src/views/page-builder/canvas/BuilderCanvas.tsx +17 -20
- package/src/views/page-builder/canvas/ColumnRenderer.tsx +22 -26
- package/src/views/page-builder/canvas/ContainerRenderer.tsx +20 -24
- package/src/views/page-builder/canvas/RowRenderer.tsx +19 -23
- package/src/views/page-builder/canvas/SectionRenderer.tsx +30 -34
- package/src/views/page-builder/canvas/index.ts +2 -2
package/src/views/Settings.tsx
CHANGED
|
@@ -1,122 +1,158 @@
|
|
|
1
|
-
'use client'
|
|
1
|
+
'use client'
|
|
2
2
|
|
|
3
|
-
import * as Tabs from '@radix-ui/react-tabs'
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
import * as Tabs from '@radix-ui/react-tabs'
|
|
4
|
+
import {
|
|
5
|
+
Bot,
|
|
6
|
+
Eye,
|
|
7
|
+
EyeOff,
|
|
8
|
+
Image,
|
|
9
|
+
FileCode2,
|
|
10
|
+
BookOpen,
|
|
11
|
+
Sparkles,
|
|
12
|
+
MessageSquare,
|
|
13
|
+
Languages,
|
|
14
|
+
Loader2,
|
|
15
|
+
AlertTriangle,
|
|
16
|
+
Download,
|
|
17
|
+
CheckCircle2,
|
|
18
|
+
ArrowUpCircle,
|
|
19
|
+
ExternalLink,
|
|
20
|
+
RefreshCw,
|
|
21
|
+
GitPullRequest,
|
|
22
|
+
Layers,
|
|
23
|
+
} from 'lucide-react'
|
|
24
|
+
import { useState, useEffect } from 'react'
|
|
25
|
+
import { toast } from 'sonner'
|
|
26
|
+
import { useApiData } from '../lib/useApiData.js'
|
|
27
|
+
import { cmsApi } from '../lib/api.js'
|
|
28
|
+
import { useTheme } from '../components/ThemeProvider.js'
|
|
29
|
+
import { RelationshipField } from '../fields/RelationshipField.js'
|
|
11
30
|
|
|
12
31
|
export interface SettingsProps {
|
|
13
|
-
onNavigate?: (path: string) => void
|
|
14
|
-
config?: any
|
|
32
|
+
onNavigate?: (path: string) => void
|
|
33
|
+
config?: any
|
|
15
34
|
}
|
|
16
35
|
|
|
17
36
|
export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
18
|
-
const { data, loading, error, refetch } = useApiData<any>('/globals/settings')
|
|
19
|
-
|
|
20
|
-
const [siteTitle, setSiteTitle] = useState('My CMS')
|
|
21
|
-
const [tagline, setTagline] = useState('A lightweight content management system')
|
|
22
|
-
const [siteUrl, setSiteUrl] = useState('https://example.com')
|
|
23
|
-
const [language, setLanguage] = useState('en')
|
|
24
|
-
const [timezone, setTimezone] = useState('UTC')
|
|
25
|
-
const [defaultNoIndex, setDefaultNoIndex] = useState(false)
|
|
26
|
-
const [defaultNoFollow, setDefaultNoFollow] = useState(false)
|
|
27
|
-
const [noIndexNonProduction, setNoIndexNonProduction] = useState(false)
|
|
28
|
-
const [twoFactorEnabled, setTwoFactorEnabled] = useState(false)
|
|
29
|
-
const [sessionTimeout, setSessionTimeout] = useState(false)
|
|
30
|
-
const [ipWhitelist, setIpWhitelist] = useState(false)
|
|
31
|
-
const [activeTab, setActiveTab] = useState('general')
|
|
32
|
-
const [saving, setSaving] = useState(false)
|
|
37
|
+
const { data, loading, error, refetch } = useApiData<any>('/globals/settings')
|
|
38
|
+
|
|
39
|
+
const [siteTitle, setSiteTitle] = useState('My CMS')
|
|
40
|
+
const [tagline, setTagline] = useState('A lightweight content management system')
|
|
41
|
+
const [siteUrl, setSiteUrl] = useState('https://example.com')
|
|
42
|
+
const [language, setLanguage] = useState('en')
|
|
43
|
+
const [timezone, setTimezone] = useState('UTC')
|
|
44
|
+
const [defaultNoIndex, setDefaultNoIndex] = useState(false)
|
|
45
|
+
const [defaultNoFollow, setDefaultNoFollow] = useState(false)
|
|
46
|
+
const [noIndexNonProduction, setNoIndexNonProduction] = useState(false)
|
|
47
|
+
const [twoFactorEnabled, setTwoFactorEnabled] = useState(false)
|
|
48
|
+
const [sessionTimeout, setSessionTimeout] = useState(false)
|
|
49
|
+
const [ipWhitelist, setIpWhitelist] = useState(false)
|
|
50
|
+
const [activeTab, setActiveTab] = useState('general')
|
|
51
|
+
const [saving, setSaving] = useState(false)
|
|
33
52
|
|
|
34
53
|
// Layout defaults
|
|
35
|
-
const [defaultLayout, setDefaultLayout] = useState<Record<string, string>>({})
|
|
36
|
-
const layoutConfig = config?.layout
|
|
37
|
-
const layoutRegions: Array<{ name: string; collection: string; label: string }> =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
const [defaultLayout, setDefaultLayout] = useState<Record<string, string>>({})
|
|
55
|
+
const layoutConfig = config?.layout
|
|
56
|
+
const layoutRegions: Array<{ name: string; collection: string; label: string }> =
|
|
57
|
+
layoutConfig?.regions
|
|
58
|
+
? Object.entries(layoutConfig.regions).map(([name, region]: [string, any]) => ({
|
|
59
|
+
name,
|
|
60
|
+
collection: region.collection,
|
|
61
|
+
label: region.label ?? name.charAt(0).toUpperCase() + name.slice(1),
|
|
62
|
+
}))
|
|
63
|
+
: []
|
|
64
|
+
const hasLayoutRegions = layoutRegions.length > 0
|
|
45
65
|
|
|
46
66
|
// AI settings
|
|
47
|
-
const [aiProvider, setAiProvider] = useState('anthropic')
|
|
48
|
-
const [aiApiKey, setAiApiKey] = useState('')
|
|
49
|
-
const [showApiKey, setShowApiKey] = useState(false)
|
|
50
|
-
const [aiAltTags, setAiAltTags] = useState(true)
|
|
51
|
-
const [aiMediaCategorize, setAiMediaCategorize] = useState(false)
|
|
52
|
-
const [aiMetaDescriptions, setAiMetaDescriptions] = useState(true)
|
|
53
|
-
const [aiReadability, setAiReadability] = useState(true)
|
|
54
|
-
const [aiSchema, setAiSchema] = useState(true)
|
|
55
|
-
const [aiBrandVoice, setAiBrandVoice] = useState(false)
|
|
56
|
-
const [aiWritingAssistant, setAiWritingAssistant] = useState(true)
|
|
57
|
-
const [aiContentScoring, setAiContentScoring] = useState(true)
|
|
58
|
-
const [aiTranslation, setAiTranslation] = useState(false)
|
|
67
|
+
const [aiProvider, setAiProvider] = useState('anthropic')
|
|
68
|
+
const [aiApiKey, setAiApiKey] = useState('')
|
|
69
|
+
const [showApiKey, setShowApiKey] = useState(false)
|
|
70
|
+
const [aiAltTags, setAiAltTags] = useState(true)
|
|
71
|
+
const [aiMediaCategorize, setAiMediaCategorize] = useState(false)
|
|
72
|
+
const [aiMetaDescriptions, setAiMetaDescriptions] = useState(true)
|
|
73
|
+
const [aiReadability, setAiReadability] = useState(true)
|
|
74
|
+
const [aiSchema, setAiSchema] = useState(true)
|
|
75
|
+
const [aiBrandVoice, setAiBrandVoice] = useState(false)
|
|
76
|
+
const [aiWritingAssistant, setAiWritingAssistant] = useState(true)
|
|
77
|
+
const [aiContentScoring, setAiContentScoring] = useState(true)
|
|
78
|
+
const [aiTranslation, setAiTranslation] = useState(false)
|
|
59
79
|
|
|
60
80
|
useEffect(() => {
|
|
61
81
|
if (data) {
|
|
62
|
-
setSiteTitle(data.siteTitle ?? 'My CMS')
|
|
63
|
-
setTagline(data.tagline ?? '')
|
|
64
|
-
setSiteUrl(data.siteUrl ?? '')
|
|
65
|
-
setLanguage(data.language ?? 'en')
|
|
66
|
-
setTimezone(data.timezone ?? 'UTC')
|
|
67
|
-
setDefaultNoIndex(data.defaultNoIndex ?? false)
|
|
68
|
-
setDefaultNoFollow(data.defaultNoFollow ?? false)
|
|
69
|
-
setNoIndexNonProduction(data.noIndexNonProduction ?? false)
|
|
70
|
-
setTwoFactorEnabled(data.twoFactorEnabled ?? false)
|
|
71
|
-
setSessionTimeout(data.sessionTimeout ?? false)
|
|
72
|
-
setIpWhitelist(data.ipWhitelist ?? false)
|
|
73
|
-
setAiProvider(data.aiProvider ?? 'anthropic')
|
|
74
|
-
setAiAltTags(data.aiAltTags ?? true)
|
|
75
|
-
setAiMediaCategorize(data.aiMediaCategorize ?? false)
|
|
76
|
-
setAiMetaDescriptions(data.aiMetaDescriptions ?? true)
|
|
77
|
-
setAiReadability(data.aiReadability ?? true)
|
|
78
|
-
setAiSchema(data.aiSchema ?? true)
|
|
79
|
-
setAiBrandVoice(data.aiBrandVoice ?? false)
|
|
80
|
-
setAiWritingAssistant(data.aiWritingAssistant ?? true)
|
|
81
|
-
setAiContentScoring(data.aiContentScoring ?? true)
|
|
82
|
-
setAiTranslation(data.aiTranslation ?? false)
|
|
82
|
+
setSiteTitle(data.siteTitle ?? 'My CMS')
|
|
83
|
+
setTagline(data.tagline ?? '')
|
|
84
|
+
setSiteUrl(data.siteUrl ?? '')
|
|
85
|
+
setLanguage(data.language ?? 'en')
|
|
86
|
+
setTimezone(data.timezone ?? 'UTC')
|
|
87
|
+
setDefaultNoIndex(data.defaultNoIndex ?? false)
|
|
88
|
+
setDefaultNoFollow(data.defaultNoFollow ?? false)
|
|
89
|
+
setNoIndexNonProduction(data.noIndexNonProduction ?? false)
|
|
90
|
+
setTwoFactorEnabled(data.twoFactorEnabled ?? false)
|
|
91
|
+
setSessionTimeout(data.sessionTimeout ?? false)
|
|
92
|
+
setIpWhitelist(data.ipWhitelist ?? false)
|
|
93
|
+
setAiProvider(data.aiProvider ?? 'anthropic')
|
|
94
|
+
setAiAltTags(data.aiAltTags ?? true)
|
|
95
|
+
setAiMediaCategorize(data.aiMediaCategorize ?? false)
|
|
96
|
+
setAiMetaDescriptions(data.aiMetaDescriptions ?? true)
|
|
97
|
+
setAiReadability(data.aiReadability ?? true)
|
|
98
|
+
setAiSchema(data.aiSchema ?? true)
|
|
99
|
+
setAiBrandVoice(data.aiBrandVoice ?? false)
|
|
100
|
+
setAiWritingAssistant(data.aiWritingAssistant ?? true)
|
|
101
|
+
setAiContentScoring(data.aiContentScoring ?? true)
|
|
102
|
+
setAiTranslation(data.aiTranslation ?? false)
|
|
83
103
|
if (data.defaultLayout && typeof data.defaultLayout === 'object') {
|
|
84
|
-
setDefaultLayout(data.defaultLayout)
|
|
104
|
+
setDefaultLayout(data.defaultLayout)
|
|
85
105
|
}
|
|
86
106
|
}
|
|
87
|
-
}, [data])
|
|
107
|
+
}, [data])
|
|
88
108
|
|
|
89
109
|
const handleSave = async () => {
|
|
90
|
-
setSaving(true)
|
|
91
|
-
const layoutPayload = Object.keys(defaultLayout).length > 0 ? { defaultLayout } : {}
|
|
110
|
+
setSaving(true)
|
|
111
|
+
const layoutPayload = Object.keys(defaultLayout).length > 0 ? { defaultLayout } : {}
|
|
92
112
|
const res = await cmsApi('/globals/settings', {
|
|
93
113
|
method: 'PUT',
|
|
94
114
|
body: JSON.stringify({
|
|
95
|
-
siteTitle,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
115
|
+
siteTitle,
|
|
116
|
+
tagline,
|
|
117
|
+
siteUrl,
|
|
118
|
+
language,
|
|
119
|
+
timezone,
|
|
120
|
+
defaultNoIndex,
|
|
121
|
+
defaultNoFollow,
|
|
122
|
+
noIndexNonProduction,
|
|
123
|
+
twoFactorEnabled,
|
|
124
|
+
sessionTimeout,
|
|
125
|
+
ipWhitelist,
|
|
126
|
+
aiProvider,
|
|
127
|
+
aiAltTags,
|
|
128
|
+
aiMediaCategorize,
|
|
129
|
+
aiMetaDescriptions,
|
|
130
|
+
aiReadability,
|
|
131
|
+
aiSchema,
|
|
132
|
+
aiBrandVoice,
|
|
133
|
+
aiWritingAssistant,
|
|
134
|
+
aiContentScoring,
|
|
135
|
+
aiTranslation,
|
|
101
136
|
...layoutPayload,
|
|
102
137
|
}),
|
|
103
|
-
})
|
|
104
|
-
setSaving(false)
|
|
138
|
+
})
|
|
139
|
+
setSaving(false)
|
|
105
140
|
if (res.error) {
|
|
106
|
-
toast.error(res.error)
|
|
141
|
+
toast.error(res.error)
|
|
107
142
|
} else {
|
|
108
|
-
toast.success('Settings saved successfully!')
|
|
143
|
+
toast.success('Settings saved successfully!')
|
|
109
144
|
}
|
|
110
|
-
}
|
|
145
|
+
}
|
|
111
146
|
|
|
112
|
-
const tabTriggerClass =
|
|
147
|
+
const tabTriggerClass =
|
|
148
|
+
'px-4 py-2 text-sm font-medium text-gray-600 transition-colors hover:text-gray-900 data-[state=active]:border-b-2 data-[state=active]:border-blue-600 data-[state=active]:text-blue-600'
|
|
113
149
|
|
|
114
150
|
if (loading) {
|
|
115
151
|
return (
|
|
116
152
|
<div className="p-3 pr-6 sm:p-4 sm:pr-8 flex items-center justify-center h-64">
|
|
117
153
|
<Loader2 className="w-6 h-6 animate-spin text-blue-600" />
|
|
118
154
|
</div>
|
|
119
|
-
)
|
|
155
|
+
)
|
|
120
156
|
}
|
|
121
157
|
|
|
122
158
|
return (
|
|
@@ -125,7 +161,12 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
125
161
|
<div className="mb-4 flex items-center gap-3 rounded-lg border border-red-200 bg-red-50 p-3">
|
|
126
162
|
<AlertTriangle className="w-5 h-5 text-red-600 shrink-0" />
|
|
127
163
|
<span className="text-sm text-red-800 flex-1">{error}</span>
|
|
128
|
-
<button
|
|
164
|
+
<button
|
|
165
|
+
onClick={refetch}
|
|
166
|
+
className="px-3 py-1 text-sm text-red-700 border border-red-300 rounded-lg hover:bg-red-100 transition-colors"
|
|
167
|
+
>
|
|
168
|
+
Retry
|
|
169
|
+
</button>
|
|
129
170
|
</div>
|
|
130
171
|
)}
|
|
131
172
|
|
|
@@ -136,8 +177,12 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
136
177
|
|
|
137
178
|
<Tabs.Root value={activeTab} onValueChange={setActiveTab}>
|
|
138
179
|
<Tabs.List className="mb-4 flex gap-1 border-b border-gray-200 overflow-x-auto">
|
|
139
|
-
<Tabs.Trigger value="general" className={tabTriggerClass}>
|
|
140
|
-
|
|
180
|
+
<Tabs.Trigger value="general" className={tabTriggerClass}>
|
|
181
|
+
General
|
|
182
|
+
</Tabs.Trigger>
|
|
183
|
+
<Tabs.Trigger value="appearance" className={tabTriggerClass}>
|
|
184
|
+
Appearance
|
|
185
|
+
</Tabs.Trigger>
|
|
141
186
|
{hasLayoutRegions && (
|
|
142
187
|
<Tabs.Trigger value="layout" className={tabTriggerClass}>
|
|
143
188
|
<span className="flex items-center gap-1.5">
|
|
@@ -146,14 +191,18 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
146
191
|
</span>
|
|
147
192
|
</Tabs.Trigger>
|
|
148
193
|
)}
|
|
149
|
-
<Tabs.Trigger value="security" className={tabTriggerClass}>
|
|
194
|
+
<Tabs.Trigger value="security" className={tabTriggerClass}>
|
|
195
|
+
Security
|
|
196
|
+
</Tabs.Trigger>
|
|
150
197
|
<Tabs.Trigger value="ai" className={tabTriggerClass}>
|
|
151
198
|
<span className="flex items-center gap-1.5">
|
|
152
199
|
<Bot className="w-4 h-4" />
|
|
153
200
|
AI
|
|
154
201
|
</span>
|
|
155
202
|
</Tabs.Trigger>
|
|
156
|
-
<Tabs.Trigger value="integrations" className={tabTriggerClass}>
|
|
203
|
+
<Tabs.Trigger value="integrations" className={tabTriggerClass}>
|
|
204
|
+
Integrations
|
|
205
|
+
</Tabs.Trigger>
|
|
157
206
|
<Tabs.Trigger value="updates" className={tabTriggerClass}>
|
|
158
207
|
<span className="flex items-center gap-1.5">
|
|
159
208
|
<Download className="w-4 h-4" />
|
|
@@ -168,15 +217,30 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
168
217
|
<div className="space-y-4">
|
|
169
218
|
<div>
|
|
170
219
|
<label className="mb-1 block text-sm font-medium text-gray-700">Site Title</label>
|
|
171
|
-
<input
|
|
220
|
+
<input
|
|
221
|
+
type="text"
|
|
222
|
+
value={siteTitle}
|
|
223
|
+
onChange={(e) => setSiteTitle(e.target.value)}
|
|
224
|
+
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
225
|
+
/>
|
|
172
226
|
</div>
|
|
173
227
|
<div>
|
|
174
228
|
<label className="mb-1 block text-sm font-medium text-gray-700">Tagline</label>
|
|
175
|
-
<input
|
|
229
|
+
<input
|
|
230
|
+
type="text"
|
|
231
|
+
value={tagline}
|
|
232
|
+
onChange={(e) => setTagline(e.target.value)}
|
|
233
|
+
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
234
|
+
/>
|
|
176
235
|
</div>
|
|
177
236
|
<div>
|
|
178
237
|
<label className="mb-1 block text-sm font-medium text-gray-700">Site URL</label>
|
|
179
|
-
<input
|
|
238
|
+
<input
|
|
239
|
+
type="url"
|
|
240
|
+
value={siteUrl}
|
|
241
|
+
onChange={(e) => setSiteUrl(e.target.value)}
|
|
242
|
+
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
243
|
+
/>
|
|
180
244
|
</div>
|
|
181
245
|
</div>
|
|
182
246
|
</div>
|
|
@@ -185,7 +249,11 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
185
249
|
<div className="space-y-4">
|
|
186
250
|
<div>
|
|
187
251
|
<label className="mb-1 block text-sm font-medium text-gray-700">Language</label>
|
|
188
|
-
<select
|
|
252
|
+
<select
|
|
253
|
+
value={language}
|
|
254
|
+
onChange={(e) => setLanguage(e.target.value)}
|
|
255
|
+
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
256
|
+
>
|
|
189
257
|
<option value="en">English</option>
|
|
190
258
|
<option value="es">Spanish</option>
|
|
191
259
|
<option value="fr">French</option>
|
|
@@ -194,7 +262,11 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
194
262
|
</div>
|
|
195
263
|
<div>
|
|
196
264
|
<label className="mb-1 block text-sm font-medium text-gray-700">Timezone</label>
|
|
197
|
-
<select
|
|
265
|
+
<select
|
|
266
|
+
value={timezone}
|
|
267
|
+
onChange={(e) => setTimezone(e.target.value)}
|
|
268
|
+
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
269
|
+
>
|
|
198
270
|
<option value="UTC">UTC</option>
|
|
199
271
|
<option value="America/New_York">Eastern Time</option>
|
|
200
272
|
<option value="America/Chicago">Central Time</option>
|
|
@@ -207,7 +279,8 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
207
279
|
<div className="rounded-lg border border-border bg-card p-4">
|
|
208
280
|
<h3 className="mb-1 text-sm font-medium text-foreground">SEO & Robots Defaults</h3>
|
|
209
281
|
<p className="mb-4 text-xs text-muted-foreground">
|
|
210
|
-
Set the site-wide default for search engine indexing. Individual pages can inherit or
|
|
282
|
+
Set the site-wide default for search engine indexing. Individual pages can inherit or
|
|
283
|
+
override these rules in their SEO panel.
|
|
211
284
|
</p>
|
|
212
285
|
<div className="space-y-4">
|
|
213
286
|
<ToggleSetting
|
|
@@ -241,8 +314,14 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
241
314
|
<ThemeSelect />
|
|
242
315
|
</div>
|
|
243
316
|
<div>
|
|
244
|
-
<label className="mb-1 block text-sm font-medium text-gray-700">
|
|
245
|
-
|
|
317
|
+
<label className="mb-1 block text-sm font-medium text-gray-700">
|
|
318
|
+
Primary Color
|
|
319
|
+
</label>
|
|
320
|
+
<input
|
|
321
|
+
type="color"
|
|
322
|
+
defaultValue="#3b82f6"
|
|
323
|
+
className="h-10 w-full rounded-lg border border-gray-300 px-3 py-2"
|
|
324
|
+
/>
|
|
246
325
|
</div>
|
|
247
326
|
</div>
|
|
248
327
|
</div>
|
|
@@ -253,7 +332,8 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
253
332
|
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
254
333
|
<h3 className="mb-1 text-sm font-semibold text-gray-900">Default Layout Variants</h3>
|
|
255
334
|
<p className="mb-4 text-xs text-gray-500">
|
|
256
|
-
Select the default header, footer, and other layout variants used site-wide. Pages
|
|
335
|
+
Select the default header, footer, and other layout variants used site-wide. Pages
|
|
336
|
+
can override these individually or inherit from parent pages.
|
|
257
337
|
</p>
|
|
258
338
|
<div className="space-y-4">
|
|
259
339
|
{layoutRegions.map((region) => (
|
|
@@ -263,14 +343,14 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
263
343
|
value={defaultLayout[region.name] ?? ''}
|
|
264
344
|
onChange={(val) => {
|
|
265
345
|
setDefaultLayout((prev) => {
|
|
266
|
-
const next = { ...prev }
|
|
346
|
+
const next = { ...prev }
|
|
267
347
|
if (val && typeof val === 'string') {
|
|
268
|
-
next[region.name] = val
|
|
348
|
+
next[region.name] = val
|
|
269
349
|
} else {
|
|
270
|
-
delete next[region.name]
|
|
350
|
+
delete next[region.name]
|
|
271
351
|
}
|
|
272
|
-
return next
|
|
273
|
-
})
|
|
352
|
+
return next
|
|
353
|
+
})
|
|
274
354
|
}}
|
|
275
355
|
relationTo={region.collection}
|
|
276
356
|
helpText={`The ${region.label.toLowerCase()} variant used when no page in the ancestor chain specifies one`}
|
|
@@ -279,19 +359,40 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
279
359
|
</div>
|
|
280
360
|
</div>
|
|
281
361
|
<div className="rounded-lg border border-gray-200 bg-gray-50 p-4">
|
|
282
|
-
<h3 className="text-sm font-semibold text-gray-700 mb-2">
|
|
362
|
+
<h3 className="text-sm font-semibold text-gray-700 mb-2">
|
|
363
|
+
How Layout Inheritance Works
|
|
364
|
+
</h3>
|
|
283
365
|
<ul className="space-y-1.5 text-xs text-gray-600">
|
|
284
366
|
<li className="flex items-start gap-2">
|
|
285
|
-
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
286
|
-
|
|
367
|
+
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
368
|
+
1
|
|
369
|
+
</span>
|
|
370
|
+
<span>
|
|
371
|
+
Each page can assign specific layout variants (header, footer, etc.) from the
|
|
372
|
+
document editor.
|
|
373
|
+
</span>
|
|
287
374
|
</li>
|
|
288
375
|
<li className="flex items-start gap-2">
|
|
289
|
-
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
290
|
-
|
|
376
|
+
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
377
|
+
2
|
|
378
|
+
</span>
|
|
379
|
+
<span>
|
|
380
|
+
Child pages automatically inherit their parent's layout. For example,{' '}
|
|
381
|
+
<code className="font-mono bg-gray-200 px-1 rounded">
|
|
382
|
+
/hampton-roads/thank-you
|
|
383
|
+
</code>{' '}
|
|
384
|
+
inherits from{' '}
|
|
385
|
+
<code className="font-mono bg-gray-200 px-1 rounded">/hampton-roads</code>.
|
|
386
|
+
</span>
|
|
291
387
|
</li>
|
|
292
388
|
<li className="flex items-start gap-2">
|
|
293
|
-
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
294
|
-
|
|
389
|
+
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
390
|
+
3
|
|
391
|
+
</span>
|
|
392
|
+
<span>
|
|
393
|
+
If no page in the ancestor chain sets a variant, the defaults configured above
|
|
394
|
+
are used.
|
|
395
|
+
</span>
|
|
295
396
|
</li>
|
|
296
397
|
</ul>
|
|
297
398
|
</div>
|
|
@@ -302,9 +403,24 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
302
403
|
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
303
404
|
<h3 className="mb-4 text-sm font-semibold text-gray-900">Security Settings</h3>
|
|
304
405
|
<div className="space-y-4">
|
|
305
|
-
<ToggleSetting
|
|
306
|
-
|
|
307
|
-
|
|
406
|
+
<ToggleSetting
|
|
407
|
+
label="Two-Factor Authentication"
|
|
408
|
+
description="Require 2FA for all admin users"
|
|
409
|
+
checked={twoFactorEnabled}
|
|
410
|
+
onChange={setTwoFactorEnabled}
|
|
411
|
+
/>
|
|
412
|
+
<ToggleSetting
|
|
413
|
+
label="Session Timeout"
|
|
414
|
+
description="Automatically log out inactive users after 30 minutes"
|
|
415
|
+
checked={sessionTimeout}
|
|
416
|
+
onChange={setSessionTimeout}
|
|
417
|
+
/>
|
|
418
|
+
<ToggleSetting
|
|
419
|
+
label="IP Whitelist"
|
|
420
|
+
description="Only allow access from approved IP addresses"
|
|
421
|
+
checked={ipWhitelist}
|
|
422
|
+
onChange={setIpWhitelist}
|
|
423
|
+
/>
|
|
308
424
|
</div>
|
|
309
425
|
</div>
|
|
310
426
|
</Tabs.Content>
|
|
@@ -312,7 +428,9 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
312
428
|
<Tabs.Content value="ai" className="space-y-4">
|
|
313
429
|
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
314
430
|
<h3 className="mb-1 text-sm font-semibold text-gray-900">AI Provider & API Key</h3>
|
|
315
|
-
<p className="mb-4 text-xs text-gray-500">
|
|
431
|
+
<p className="mb-4 text-xs text-gray-500">
|
|
432
|
+
Connect an AI provider to enable intelligent content features
|
|
433
|
+
</p>
|
|
316
434
|
<div className="space-y-4">
|
|
317
435
|
<div>
|
|
318
436
|
<label className="mb-1 block text-sm font-medium text-gray-700">Provider</label>
|
|
@@ -326,9 +444,12 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
326
444
|
<option value="google">Google (Gemini)</option>
|
|
327
445
|
</select>
|
|
328
446
|
<p className="mt-1 text-xs text-gray-500">
|
|
329
|
-
{aiProvider === 'anthropic' &&
|
|
330
|
-
|
|
331
|
-
{aiProvider === '
|
|
447
|
+
{aiProvider === 'anthropic' &&
|
|
448
|
+
'Recommended. Best for content analysis, brand voice learning, and nuanced writing.'}
|
|
449
|
+
{aiProvider === 'openai' &&
|
|
450
|
+
'Strong alternative. Good for general content generation and image understanding.'}
|
|
451
|
+
{aiProvider === 'google' &&
|
|
452
|
+
'Multimodal. Excellent for image analysis and multilingual content.'}
|
|
332
453
|
</p>
|
|
333
454
|
</div>
|
|
334
455
|
<div>
|
|
@@ -339,7 +460,13 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
339
460
|
type={showApiKey ? 'text' : 'password'}
|
|
340
461
|
value={aiApiKey}
|
|
341
462
|
onChange={(e) => setAiApiKey(e.target.value)}
|
|
342
|
-
placeholder={
|
|
463
|
+
placeholder={
|
|
464
|
+
aiProvider === 'anthropic'
|
|
465
|
+
? 'sk-ant-...'
|
|
466
|
+
: aiProvider === 'openai'
|
|
467
|
+
? 'sk-...'
|
|
468
|
+
: 'AIza...'
|
|
469
|
+
}
|
|
343
470
|
className="w-full rounded-lg border border-gray-300 px-3 py-2 pr-10 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
344
471
|
/>
|
|
345
472
|
<button
|
|
@@ -353,15 +480,17 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
353
480
|
<button
|
|
354
481
|
type="button"
|
|
355
482
|
onClick={() => {
|
|
356
|
-
if (aiApiKey) toast.success('API key verified successfully')
|
|
357
|
-
else toast.error('Please enter an API key first')
|
|
483
|
+
if (aiApiKey) toast.success('API key verified successfully')
|
|
484
|
+
else toast.error('Please enter an API key first')
|
|
358
485
|
}}
|
|
359
486
|
className="rounded-lg border border-gray-300 px-4 py-2 text-sm transition-colors hover:bg-gray-50"
|
|
360
487
|
>
|
|
361
488
|
Verify
|
|
362
489
|
</button>
|
|
363
490
|
</div>
|
|
364
|
-
<p className="mt-1 text-xs text-gray-500">
|
|
491
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
492
|
+
Your key is encrypted at rest (AES-256-GCM) and never exposed to the client
|
|
493
|
+
</p>
|
|
365
494
|
</div>
|
|
366
495
|
</div>
|
|
367
496
|
</div>
|
|
@@ -393,7 +522,9 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
393
522
|
<FileCode2 className="w-4 h-4 text-purple-600" />
|
|
394
523
|
<h3 className="text-sm font-semibold text-gray-900">Content SEO</h3>
|
|
395
524
|
</div>
|
|
396
|
-
<p className="mb-4 text-xs text-gray-500">
|
|
525
|
+
<p className="mb-4 text-xs text-gray-500">
|
|
526
|
+
AI-driven SEO optimization for your content
|
|
527
|
+
</p>
|
|
397
528
|
<div className="space-y-4">
|
|
398
529
|
<ToggleSetting
|
|
399
530
|
label="Meta Description Generation"
|
|
@@ -421,7 +552,9 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
421
552
|
<MessageSquare className="w-4 h-4 text-green-600" />
|
|
422
553
|
<h3 className="text-sm font-semibold text-gray-900">Brand Voice & Writing</h3>
|
|
423
554
|
</div>
|
|
424
|
-
<p className="mb-4 text-xs text-gray-500">
|
|
555
|
+
<p className="mb-4 text-xs text-gray-500">
|
|
556
|
+
AI that understands and writes in your brand's voice
|
|
557
|
+
</p>
|
|
425
558
|
<div className="space-y-4">
|
|
426
559
|
<ToggleSetting
|
|
427
560
|
label="Brand Voice Training"
|
|
@@ -443,7 +576,9 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
443
576
|
<Sparkles className="w-4 h-4 text-yellow-600" />
|
|
444
577
|
<h3 className="text-sm font-semibold text-gray-900">Content Quality</h3>
|
|
445
578
|
</div>
|
|
446
|
-
<p className="mb-4 text-xs text-gray-500">
|
|
579
|
+
<p className="mb-4 text-xs text-gray-500">
|
|
580
|
+
Automated quality scoring and content intelligence
|
|
581
|
+
</p>
|
|
447
582
|
<div className="space-y-4">
|
|
448
583
|
<ToggleSetting
|
|
449
584
|
label="Content Scoring"
|
|
@@ -479,7 +614,9 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
479
614
|
<div className="text-xs text-indigo-700">Estimated Cost</div>
|
|
480
615
|
</div>
|
|
481
616
|
</div>
|
|
482
|
-
<p className="mt-2 text-xs text-indigo-600">
|
|
617
|
+
<p className="mt-2 text-xs text-indigo-600">
|
|
618
|
+
Add an API key above to start using AI features
|
|
619
|
+
</p>
|
|
483
620
|
</div>
|
|
484
621
|
</Tabs.Content>
|
|
485
622
|
|
|
@@ -490,8 +627,18 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
490
627
|
<div>
|
|
491
628
|
<label className="mb-1 block text-sm font-medium text-gray-700">API Key</label>
|
|
492
629
|
<div className="flex gap-2">
|
|
493
|
-
<input
|
|
494
|
-
|
|
630
|
+
<input
|
|
631
|
+
type="text"
|
|
632
|
+
value="•••••••••••••••••••••••••"
|
|
633
|
+
readOnly
|
|
634
|
+
className="flex-1 rounded-lg border border-gray-300 bg-gray-50 px-3 py-2 text-sm"
|
|
635
|
+
/>
|
|
636
|
+
<button
|
|
637
|
+
type="button"
|
|
638
|
+
className="rounded-lg border border-gray-300 px-4 py-2 text-sm transition-colors hover:bg-gray-50"
|
|
639
|
+
>
|
|
640
|
+
Regenerate
|
|
641
|
+
</button>
|
|
495
642
|
</div>
|
|
496
643
|
</div>
|
|
497
644
|
</div>
|
|
@@ -514,11 +661,11 @@ export function Settings({ config, ..._props }: SettingsProps = {}) {
|
|
|
514
661
|
</button>
|
|
515
662
|
</div>
|
|
516
663
|
</div>
|
|
517
|
-
)
|
|
664
|
+
)
|
|
518
665
|
}
|
|
519
666
|
|
|
520
667
|
function ThemeSelect() {
|
|
521
|
-
const { theme, setTheme } = useTheme()
|
|
668
|
+
const { theme, setTheme } = useTheme()
|
|
522
669
|
return (
|
|
523
670
|
<select
|
|
524
671
|
value={theme}
|
|
@@ -529,7 +676,7 @@ function ThemeSelect() {
|
|
|
529
676
|
<option value="dark">Dark</option>
|
|
530
677
|
<option value="system">Auto (System)</option>
|
|
531
678
|
</select>
|
|
532
|
-
)
|
|
679
|
+
)
|
|
533
680
|
}
|
|
534
681
|
|
|
535
682
|
function ToggleSetting({
|
|
@@ -538,10 +685,10 @@ function ToggleSetting({
|
|
|
538
685
|
checked,
|
|
539
686
|
onChange,
|
|
540
687
|
}: {
|
|
541
|
-
label: string
|
|
542
|
-
description: string
|
|
543
|
-
checked: boolean
|
|
544
|
-
onChange: (v: boolean) => void
|
|
688
|
+
label: string
|
|
689
|
+
description: string
|
|
690
|
+
checked: boolean
|
|
691
|
+
onChange: (v: boolean) => void
|
|
545
692
|
}) {
|
|
546
693
|
return (
|
|
547
694
|
<div className="flex items-center justify-between gap-4">
|
|
@@ -564,7 +711,7 @@ function ToggleSetting({
|
|
|
564
711
|
/>
|
|
565
712
|
</button>
|
|
566
713
|
</div>
|
|
567
|
-
)
|
|
714
|
+
)
|
|
568
715
|
}
|
|
569
716
|
|
|
570
717
|
// ---------------------------------------------------------------------------
|
|
@@ -572,56 +719,56 @@ function ToggleSetting({
|
|
|
572
719
|
// ---------------------------------------------------------------------------
|
|
573
720
|
|
|
574
721
|
interface UpdateInfo {
|
|
575
|
-
current: string
|
|
576
|
-
latest: string
|
|
577
|
-
updateAvailable: boolean
|
|
578
|
-
severity?: 'patch' | 'minor' | 'major'
|
|
579
|
-
releaseDate?: string
|
|
580
|
-
changelog?: Array<{ version: string; date: string; summary: string }
|
|
581
|
-
updateCommand?: string
|
|
582
|
-
hasGithubToken?: boolean
|
|
583
|
-
githubRepo?: string
|
|
722
|
+
current: string
|
|
723
|
+
latest: string
|
|
724
|
+
updateAvailable: boolean
|
|
725
|
+
severity?: 'patch' | 'minor' | 'major'
|
|
726
|
+
releaseDate?: string
|
|
727
|
+
changelog?: Array<{ version: string; date: string; summary: string }>
|
|
728
|
+
updateCommand?: string
|
|
729
|
+
hasGithubToken?: boolean
|
|
730
|
+
githubRepo?: string
|
|
584
731
|
}
|
|
585
732
|
|
|
586
733
|
function UpdatesPanel() {
|
|
587
|
-
const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null)
|
|
588
|
-
const [checking, setChecking] = useState(false)
|
|
589
|
-
const [checkError, setCheckError] = useState('')
|
|
590
|
-
const [applying, setApplying] = useState(false)
|
|
591
|
-
const [prResult, setPrResult] = useState<{ prUrl: string; prNumber: number } | null>(null)
|
|
592
|
-
const [hasChecked, setHasChecked] = useState(false)
|
|
734
|
+
const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null)
|
|
735
|
+
const [checking, setChecking] = useState(false)
|
|
736
|
+
const [checkError, setCheckError] = useState('')
|
|
737
|
+
const [applying, setApplying] = useState(false)
|
|
738
|
+
const [prResult, setPrResult] = useState<{ prUrl: string; prNumber: number } | null>(null)
|
|
739
|
+
const [hasChecked, setHasChecked] = useState(false)
|
|
593
740
|
|
|
594
741
|
// GitHub config
|
|
595
|
-
const [ghToken, setGhToken] = useState('')
|
|
596
|
-
const [ghRepo, setGhRepo] = useState('')
|
|
597
|
-
const [showGhToken, setShowGhToken] = useState(false)
|
|
598
|
-
const [savingConfig, setSavingConfig] = useState(false)
|
|
599
|
-
const [configSaved, setConfigSaved] = useState(false)
|
|
742
|
+
const [ghToken, setGhToken] = useState('')
|
|
743
|
+
const [ghRepo, setGhRepo] = useState('')
|
|
744
|
+
const [showGhToken, setShowGhToken] = useState(false)
|
|
745
|
+
const [savingConfig, setSavingConfig] = useState(false)
|
|
746
|
+
const [configSaved, setConfigSaved] = useState(false)
|
|
600
747
|
|
|
601
748
|
const checkForUpdates = async () => {
|
|
602
|
-
setChecking(true)
|
|
603
|
-
setCheckError('')
|
|
604
|
-
setPrResult(null)
|
|
749
|
+
setChecking(true)
|
|
750
|
+
setCheckError('')
|
|
751
|
+
setPrResult(null)
|
|
605
752
|
|
|
606
753
|
try {
|
|
607
|
-
const res = await cmsApi<UpdateInfo>('/updates/check')
|
|
754
|
+
const res = await cmsApi<UpdateInfo>('/updates/check')
|
|
608
755
|
if (res.error) {
|
|
609
|
-
setCheckError(res.error)
|
|
756
|
+
setCheckError(res.error)
|
|
610
757
|
} else if (res.data) {
|
|
611
|
-
setUpdateInfo(res.data)
|
|
612
|
-
if (res.data.githubRepo) setGhRepo(res.data.githubRepo)
|
|
758
|
+
setUpdateInfo(res.data)
|
|
759
|
+
if (res.data.githubRepo) setGhRepo(res.data.githubRepo)
|
|
613
760
|
}
|
|
614
761
|
} catch {
|
|
615
|
-
setCheckError('Unable to check for updates. Please try again.')
|
|
762
|
+
setCheckError('Unable to check for updates. Please try again.')
|
|
616
763
|
} finally {
|
|
617
|
-
setChecking(false)
|
|
618
|
-
setHasChecked(true)
|
|
764
|
+
setChecking(false)
|
|
765
|
+
setHasChecked(true)
|
|
619
766
|
}
|
|
620
|
-
}
|
|
767
|
+
}
|
|
621
768
|
|
|
622
769
|
const saveGitHubConfig = async () => {
|
|
623
|
-
setSavingConfig(true)
|
|
624
|
-
setConfigSaved(false)
|
|
770
|
+
setSavingConfig(true)
|
|
771
|
+
setConfigSaved(false)
|
|
625
772
|
try {
|
|
626
773
|
const res = await cmsApi('/updates/config', {
|
|
627
774
|
method: 'PUT',
|
|
@@ -629,56 +776,64 @@ function UpdatesPanel() {
|
|
|
629
776
|
...(ghToken ? { githubToken: ghToken } : {}),
|
|
630
777
|
...(ghRepo ? { githubRepo: ghRepo } : {}),
|
|
631
778
|
}),
|
|
632
|
-
})
|
|
779
|
+
})
|
|
633
780
|
if (res.error) {
|
|
634
|
-
toast.error(res.error)
|
|
781
|
+
toast.error(res.error)
|
|
635
782
|
} else {
|
|
636
|
-
toast.success('GitHub configuration saved and encrypted.')
|
|
637
|
-
setConfigSaved(true)
|
|
638
|
-
setGhToken('')
|
|
783
|
+
toast.success('GitHub configuration saved and encrypted.')
|
|
784
|
+
setConfigSaved(true)
|
|
785
|
+
setGhToken('')
|
|
639
786
|
if (updateInfo) {
|
|
640
|
-
setUpdateInfo({ ...updateInfo, hasGithubToken: true, githubRepo: ghRepo })
|
|
787
|
+
setUpdateInfo({ ...updateInfo, hasGithubToken: true, githubRepo: ghRepo })
|
|
641
788
|
}
|
|
642
789
|
}
|
|
643
790
|
} catch {
|
|
644
|
-
toast.error('Failed to save configuration.')
|
|
791
|
+
toast.error('Failed to save configuration.')
|
|
645
792
|
} finally {
|
|
646
|
-
setSavingConfig(false)
|
|
793
|
+
setSavingConfig(false)
|
|
647
794
|
}
|
|
648
|
-
}
|
|
795
|
+
}
|
|
649
796
|
|
|
650
797
|
const applyUpdate = async () => {
|
|
651
|
-
if (!updateInfo?.latest) return
|
|
652
|
-
setApplying(true)
|
|
798
|
+
if (!updateInfo?.latest) return
|
|
799
|
+
setApplying(true)
|
|
653
800
|
|
|
654
801
|
try {
|
|
655
802
|
const res = await cmsApi<{ prUrl: string; prNumber: number }>('/updates/apply', {
|
|
656
803
|
method: 'POST',
|
|
657
804
|
body: JSON.stringify({ targetVersion: updateInfo.latest }),
|
|
658
|
-
})
|
|
805
|
+
})
|
|
659
806
|
|
|
660
807
|
if (res.error) {
|
|
661
|
-
toast.error(res.error)
|
|
808
|
+
toast.error(res.error)
|
|
662
809
|
} else if (res.data) {
|
|
663
|
-
setPrResult(res.data)
|
|
664
|
-
toast.success('Update PR created successfully!')
|
|
810
|
+
setPrResult(res.data)
|
|
811
|
+
toast.success('Update PR created successfully!')
|
|
665
812
|
}
|
|
666
813
|
} catch {
|
|
667
|
-
toast.error('Failed to create update PR. Check your GitHub configuration.')
|
|
814
|
+
toast.error('Failed to create update PR. Check your GitHub configuration.')
|
|
668
815
|
} finally {
|
|
669
|
-
setApplying(false)
|
|
816
|
+
setApplying(false)
|
|
670
817
|
}
|
|
671
|
-
}
|
|
818
|
+
}
|
|
672
819
|
|
|
673
820
|
useEffect(() => {
|
|
674
|
-
checkForUpdates()
|
|
675
|
-
}, [])
|
|
821
|
+
checkForUpdates()
|
|
822
|
+
}, [])
|
|
676
823
|
|
|
677
|
-
const severityColors: Record<
|
|
824
|
+
const severityColors: Record<
|
|
825
|
+
string,
|
|
826
|
+
{ bg: string; text: string; border: string; label: string }
|
|
827
|
+
> = {
|
|
678
828
|
patch: { bg: 'bg-blue-50', text: 'text-blue-700', border: 'border-blue-200', label: 'Patch' },
|
|
679
|
-
minor: {
|
|
829
|
+
minor: {
|
|
830
|
+
bg: 'bg-yellow-50',
|
|
831
|
+
text: 'text-yellow-700',
|
|
832
|
+
border: 'border-yellow-200',
|
|
833
|
+
label: 'Minor',
|
|
834
|
+
},
|
|
680
835
|
major: { bg: 'bg-red-50', text: 'text-red-700', border: 'border-red-200', label: 'Major' },
|
|
681
|
-
}
|
|
836
|
+
}
|
|
682
837
|
|
|
683
838
|
return (
|
|
684
839
|
<>
|
|
@@ -688,7 +843,8 @@ function UpdatesPanel() {
|
|
|
688
843
|
<div>
|
|
689
844
|
<h3 className="text-sm font-semibold text-gray-900">Actuate CMS</h3>
|
|
690
845
|
<p className="mt-1 text-sm text-gray-600">
|
|
691
|
-
Current version:
|
|
846
|
+
Current version:{' '}
|
|
847
|
+
<span className="font-mono font-medium">{updateInfo?.current ?? '...'}</span>
|
|
692
848
|
</p>
|
|
693
849
|
</div>
|
|
694
850
|
<button
|
|
@@ -708,7 +864,12 @@ function UpdatesPanel() {
|
|
|
708
864
|
<div className="flex items-center gap-3 rounded-lg border border-red-200 bg-red-50 p-3">
|
|
709
865
|
<AlertTriangle className="w-5 h-5 text-red-600 shrink-0" />
|
|
710
866
|
<span className="text-sm text-red-800 flex-1">{checkError}</span>
|
|
711
|
-
<button
|
|
867
|
+
<button
|
|
868
|
+
onClick={checkForUpdates}
|
|
869
|
+
className="px-3 py-1 text-sm text-red-700 border border-red-300 rounded-lg hover:bg-red-100 transition-colors"
|
|
870
|
+
>
|
|
871
|
+
Retry
|
|
872
|
+
</button>
|
|
712
873
|
</div>
|
|
713
874
|
)}
|
|
714
875
|
|
|
@@ -719,7 +880,8 @@ function UpdatesPanel() {
|
|
|
719
880
|
<div>
|
|
720
881
|
<h3 className="text-sm font-semibold text-green-900">You're up to date!</h3>
|
|
721
882
|
<p className="text-sm text-green-700 mt-0.5">
|
|
722
|
-
Actuate CMS <span className="font-mono">{updateInfo.current}</span> is the latest
|
|
883
|
+
Actuate CMS <span className="font-mono">{updateInfo.current}</span> is the latest
|
|
884
|
+
version.
|
|
723
885
|
</p>
|
|
724
886
|
</div>
|
|
725
887
|
</div>
|
|
@@ -728,24 +890,33 @@ function UpdatesPanel() {
|
|
|
728
890
|
{/* Update Available */}
|
|
729
891
|
{updateInfo?.updateAvailable && (
|
|
730
892
|
<>
|
|
731
|
-
<div
|
|
893
|
+
<div
|
|
894
|
+
className={`rounded-lg border p-4 ${severityColors[updateInfo.severity ?? 'patch']?.border ?? 'border-blue-200'} ${severityColors[updateInfo.severity ?? 'patch']?.bg ?? 'bg-blue-50'}`}
|
|
895
|
+
>
|
|
732
896
|
<div className="flex items-start gap-3">
|
|
733
|
-
<ArrowUpCircle
|
|
897
|
+
<ArrowUpCircle
|
|
898
|
+
className={`w-6 h-6 mt-0.5 shrink-0 ${severityColors[updateInfo.severity ?? 'patch']?.text ?? 'text-blue-700'}`}
|
|
899
|
+
/>
|
|
734
900
|
<div className="flex-1">
|
|
735
901
|
<div className="flex items-center gap-2">
|
|
736
|
-
<h3
|
|
902
|
+
<h3
|
|
903
|
+
className={`text-sm font-semibold ${severityColors[updateInfo.severity ?? 'patch']?.text ?? 'text-blue-700'}`}
|
|
904
|
+
>
|
|
737
905
|
Update Available
|
|
738
906
|
</h3>
|
|
739
|
-
<span
|
|
907
|
+
<span
|
|
908
|
+
className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${severityColors[updateInfo.severity ?? 'patch']?.bg ?? 'bg-blue-50'} ${severityColors[updateInfo.severity ?? 'patch']?.text ?? 'text-blue-700'} border ${severityColors[updateInfo.severity ?? 'patch']?.border ?? 'border-blue-200'}`}
|
|
909
|
+
>
|
|
740
910
|
{severityColors[updateInfo.severity ?? 'patch']?.label ?? 'Update'}
|
|
741
911
|
</span>
|
|
742
912
|
</div>
|
|
743
913
|
<p className="text-sm mt-1" style={{ color: 'inherit' }}>
|
|
744
|
-
<span className="font-mono">{updateInfo.current}</span>
|
|
745
|
-
{' '}→{' '}
|
|
914
|
+
<span className="font-mono">{updateInfo.current}</span> →{' '}
|
|
746
915
|
<span className="font-mono font-semibold">{updateInfo.latest}</span>
|
|
747
916
|
{updateInfo.releaseDate && (
|
|
748
|
-
<span className="text-xs ml-2 opacity-70">
|
|
917
|
+
<span className="text-xs ml-2 opacity-70">
|
|
918
|
+
Released {updateInfo.releaseDate}
|
|
919
|
+
</span>
|
|
749
920
|
)}
|
|
750
921
|
</p>
|
|
751
922
|
</div>
|
|
@@ -790,7 +961,9 @@ function UpdatesPanel() {
|
|
|
790
961
|
<div className="space-y-2 max-h-64 overflow-y-auto">
|
|
791
962
|
{updateInfo.changelog.map((entry) => (
|
|
792
963
|
<div key={entry.version} className="flex items-baseline gap-3 text-sm">
|
|
793
|
-
<span className="font-mono text-xs text-gray-500 shrink-0 w-14">
|
|
964
|
+
<span className="font-mono text-xs text-gray-500 shrink-0 w-14">
|
|
965
|
+
{entry.version}
|
|
966
|
+
</span>
|
|
794
967
|
<span className="text-xs text-gray-400 shrink-0 w-20">{entry.date}</span>
|
|
795
968
|
<span className="text-gray-700">{entry.summary}</span>
|
|
796
969
|
</div>
|
|
@@ -809,8 +982,12 @@ function UpdatesPanel() {
|
|
|
809
982
|
<div>
|
|
810
983
|
<h3 className="text-sm font-semibold text-green-900">Pull Request Created</h3>
|
|
811
984
|
<p className="text-sm text-green-700 mt-1">
|
|
812
|
-
PR #{prResult.prNumber} has been created on your repository.
|
|
813
|
-
|
|
985
|
+
PR #{prResult.prNumber} has been created on your repository. Review and merge it to
|
|
986
|
+
apply the update, then run{' '}
|
|
987
|
+
<code className="text-xs font-mono bg-green-100 px-1 rounded">
|
|
988
|
+
npx prisma migrate deploy
|
|
989
|
+
</code>
|
|
990
|
+
.
|
|
814
991
|
</p>
|
|
815
992
|
<a
|
|
816
993
|
href={prResult.prUrl}
|
|
@@ -830,7 +1007,8 @@ function UpdatesPanel() {
|
|
|
830
1007
|
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
831
1008
|
<h3 className="text-sm font-semibold text-gray-900 mb-1">GitHub Integration</h3>
|
|
832
1009
|
<p className="text-xs text-gray-500 mb-4">
|
|
833
|
-
Connect your repository to enable one-click update PRs. Credentials are encrypted at rest
|
|
1010
|
+
Connect your repository to enable one-click update PRs. Credentials are encrypted at rest
|
|
1011
|
+
(AES-256-GCM).
|
|
834
1012
|
</p>
|
|
835
1013
|
|
|
836
1014
|
<div className="space-y-4">
|
|
@@ -843,7 +1021,12 @@ function UpdatesPanel() {
|
|
|
843
1021
|
placeholder="owner/repo"
|
|
844
1022
|
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
845
1023
|
/>
|
|
846
|
-
<p className="mt-1 text-xs text-gray-500">
|
|
1024
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
1025
|
+
e.g.{' '}
|
|
1026
|
+
<code className="font-mono bg-gray-100 px-1 rounded">
|
|
1027
|
+
actuate-media/my-client-site
|
|
1028
|
+
</code>
|
|
1029
|
+
</p>
|
|
847
1030
|
</div>
|
|
848
1031
|
<div>
|
|
849
1032
|
<label className="mb-1 block text-sm font-medium text-gray-700">
|
|
@@ -858,7 +1041,9 @@ function UpdatesPanel() {
|
|
|
858
1041
|
type={showGhToken ? 'text' : 'password'}
|
|
859
1042
|
value={ghToken}
|
|
860
1043
|
onChange={(e) => setGhToken(e.target.value)}
|
|
861
|
-
placeholder={
|
|
1044
|
+
placeholder={
|
|
1045
|
+
updateInfo?.hasGithubToken ? '••••••••••••••••' : 'ghp_... or github_pat_...'
|
|
1046
|
+
}
|
|
862
1047
|
className="w-full rounded-lg border border-gray-300 px-3 py-2 pr-10 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
863
1048
|
/>
|
|
864
1049
|
<button
|
|
@@ -871,8 +1056,14 @@ function UpdatesPanel() {
|
|
|
871
1056
|
</div>
|
|
872
1057
|
</div>
|
|
873
1058
|
<p className="mt-1 text-xs text-gray-500">
|
|
874
|
-
Needs <code className="font-mono bg-gray-100 px-1 rounded">repo</code> scope. Create
|
|
875
|
-
|
|
1059
|
+
Needs <code className="font-mono bg-gray-100 px-1 rounded">repo</code> scope. Create
|
|
1060
|
+
at{' '}
|
|
1061
|
+
<a
|
|
1062
|
+
href="https://github.com/settings/tokens"
|
|
1063
|
+
target="_blank"
|
|
1064
|
+
rel="noopener noreferrer"
|
|
1065
|
+
className="text-blue-600 hover:underline"
|
|
1066
|
+
>
|
|
876
1067
|
github.com/settings/tokens
|
|
877
1068
|
</a>
|
|
878
1069
|
</p>
|
|
@@ -901,23 +1092,42 @@ function UpdatesPanel() {
|
|
|
901
1092
|
<h3 className="text-sm font-semibold text-gray-700 mb-2">How Updates Work</h3>
|
|
902
1093
|
<ul className="space-y-1.5 text-xs text-gray-600">
|
|
903
1094
|
<li className="flex items-start gap-2">
|
|
904
|
-
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
905
|
-
|
|
1095
|
+
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
1096
|
+
1
|
|
1097
|
+
</span>
|
|
1098
|
+
<span>
|
|
1099
|
+
Click "Check for Updates" to see if a new version is available on npm.
|
|
1100
|
+
</span>
|
|
906
1101
|
</li>
|
|
907
1102
|
<li className="flex items-start gap-2">
|
|
908
|
-
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
909
|
-
|
|
1103
|
+
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
1104
|
+
2
|
|
1105
|
+
</span>
|
|
1106
|
+
<span>
|
|
1107
|
+
Add your GitHub token and repository above. They're encrypted at rest — never
|
|
1108
|
+
stored in plaintext.
|
|
1109
|
+
</span>
|
|
910
1110
|
</li>
|
|
911
1111
|
<li className="flex items-start gap-2">
|
|
912
|
-
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
913
|
-
|
|
1112
|
+
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
1113
|
+
3
|
|
1114
|
+
</span>
|
|
1115
|
+
<span>
|
|
1116
|
+
Click "Create Update PR" to open a pull request that bumps your CMS packages
|
|
1117
|
+
automatically.
|
|
1118
|
+
</span>
|
|
914
1119
|
</li>
|
|
915
1120
|
<li className="flex items-start gap-2">
|
|
916
|
-
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
917
|
-
|
|
1121
|
+
<span className="w-4 h-4 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5">
|
|
1122
|
+
4
|
|
1123
|
+
</span>
|
|
1124
|
+
<span>
|
|
1125
|
+
Review and merge the PR, then deploy. Database migrations run automatically via{' '}
|
|
1126
|
+
<code className="font-mono bg-gray-200 px-1 rounded">prisma migrate deploy</code>.
|
|
1127
|
+
</span>
|
|
918
1128
|
</li>
|
|
919
1129
|
</ul>
|
|
920
1130
|
</div>
|
|
921
1131
|
</>
|
|
922
|
-
)
|
|
1132
|
+
)
|
|
923
1133
|
}
|