fix(cms): apply dark mode to CMS editor, public CMS, and admin modals
This commit is contained in:
@@ -25,13 +25,13 @@ export const BulkArchiveModal: React.FC<BulkArchiveModalProps> = ({
|
|||||||
<Card className="w-full max-w-md">
|
<Card className="w-full max-w-md">
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h2 className="text-xl font-semibold text-neutral-900">Confirm Bulk Archive</h2>
|
<h2 className="text-xl font-semibold text-neutral-900 dark:text-neutral-100">Confirm Bulk Archive</h2>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="p-1 hover:bg-neutral-100 rounded-lg transition-colors"
|
className="p-1 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded-lg transition-colors"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
<X className="w-5 h-5 text-neutral-500" />
|
<X className="w-5 h-5 text-neutral-500 dark:text-neutral-400" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -143,8 +143,10 @@ export const CMSEditor: React.FC<CMSEditorProps> = ({ content, onChange, onSave,
|
|||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={`p-2 rounded hover:bg-neutral-100 transition-colors ${
|
className={`p-2 rounded hover:bg-neutral-100 dark:hover:bg-neutral-700 transition-colors ${
|
||||||
active ? 'bg-primary-100 text-primary-700' : 'text-neutral-700'
|
active
|
||||||
|
? 'bg-primary-100 dark:bg-primary-900/40 text-primary-700 dark:text-primary-300'
|
||||||
|
: 'text-neutral-700 dark:text-neutral-200'
|
||||||
} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
|
} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||||
title={title}
|
title={title}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -172,44 +174,32 @@ export const CMSEditor: React.FC<CMSEditorProps> = ({ content, onChange, onSave,
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Reusable view-mode chip — three states (edit/preview/split). Shared
|
||||||
|
// styling block extracted as a const so the dark variants stay in sync.
|
||||||
|
const viewModeChipClass = (mode: typeof viewMode) =>
|
||||||
|
`px-3 py-1.5 text-sm font-medium rounded transition-colors ${
|
||||||
|
viewMode === mode
|
||||||
|
? 'bg-primary-100 dark:bg-primary-900/40 text-primary-700 dark:text-primary-300'
|
||||||
|
: 'text-neutral-600 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-700'
|
||||||
|
}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`relative ${isFullscreen ? 'fixed inset-0 z-50 bg-white' : ''}`}>
|
<div className={`relative ${isFullscreen ? 'fixed inset-0 z-50 bg-white dark:bg-neutral-900' : ''}`}>
|
||||||
<div className="border border-neutral-300 rounded-lg overflow-hidden h-full flex flex-col">
|
<div className="border border-neutral-300 dark:border-neutral-700 rounded-lg overflow-hidden h-full flex flex-col bg-white dark:bg-neutral-900">
|
||||||
{/* Top Toolbar */}
|
{/* Top Toolbar */}
|
||||||
<div className="border-b border-neutral-200 bg-neutral-50">
|
<div className="border-b border-neutral-200 dark:border-neutral-700 bg-neutral-50 dark:bg-neutral-800">
|
||||||
{/* View Mode Controls */}
|
{/* View Mode Controls */}
|
||||||
<div className="flex items-center justify-between p-2 border-b border-neutral-200">
|
<div className="flex items-center justify-between p-2 border-b border-neutral-200 dark:border-neutral-700">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button onClick={() => setViewMode('edit')} className={viewModeChipClass('edit')}>
|
||||||
onClick={() => setViewMode('edit')}
|
|
||||||
className={`px-3 py-1.5 text-sm font-medium rounded transition-colors ${
|
|
||||||
viewMode === 'edit'
|
|
||||||
? 'bg-primary-100 text-primary-700'
|
|
||||||
: 'text-neutral-600 hover:bg-neutral-100'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Edit3 className="w-4 h-4 inline-block mr-1" />
|
<Edit3 className="w-4 h-4 inline-block mr-1" />
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button onClick={() => setViewMode('preview')} className={viewModeChipClass('preview')}>
|
||||||
onClick={() => setViewMode('preview')}
|
|
||||||
className={`px-3 py-1.5 text-sm font-medium rounded transition-colors ${
|
|
||||||
viewMode === 'preview'
|
|
||||||
? 'bg-primary-100 text-primary-700'
|
|
||||||
: 'text-neutral-600 hover:bg-neutral-100'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Eye className="w-4 h-4 inline-block mr-1" />
|
<Eye className="w-4 h-4 inline-block mr-1" />
|
||||||
Preview
|
Preview
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button onClick={() => setViewMode('split')} className={viewModeChipClass('split')}>
|
||||||
onClick={() => setViewMode('split')}
|
|
||||||
className={`px-3 py-1.5 text-sm font-medium rounded transition-colors ${
|
|
||||||
viewMode === 'split'
|
|
||||||
? 'bg-primary-100 text-primary-700'
|
|
||||||
: 'text-neutral-600 hover:bg-neutral-100'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Columns className="w-4 h-4 inline-block mr-1" />
|
<Columns className="w-4 h-4 inline-block mr-1" />
|
||||||
Split
|
Split
|
||||||
</button>
|
</button>
|
||||||
@@ -295,7 +285,7 @@ export const CMSEditor: React.FC<CMSEditorProps> = ({ content, onChange, onSave,
|
|||||||
<Heading6 className="w-4 h-4" />
|
<Heading6 className="w-4 h-4" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
|
|
||||||
<div className="w-px h-6 bg-neutral-300 mx-1" />
|
<div className="w-px h-6 bg-neutral-300 dark:bg-neutral-600 mx-1" />
|
||||||
|
|
||||||
<MenuButton
|
<MenuButton
|
||||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||||
@@ -329,7 +319,7 @@ export const CMSEditor: React.FC<CMSEditorProps> = ({ content, onChange, onSave,
|
|||||||
<Code2 className="w-4 h-4" />
|
<Code2 className="w-4 h-4" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
|
|
||||||
<div className="w-px h-6 bg-neutral-300 mx-1" />
|
<div className="w-px h-6 bg-neutral-300 dark:bg-neutral-600 mx-1" />
|
||||||
|
|
||||||
<MenuButton
|
<MenuButton
|
||||||
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||||
@@ -355,7 +345,7 @@ export const CMSEditor: React.FC<CMSEditorProps> = ({ content, onChange, onSave,
|
|||||||
<Quote className="w-4 h-4" />
|
<Quote className="w-4 h-4" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
|
|
||||||
<div className="w-px h-6 bg-neutral-300 mx-1" />
|
<div className="w-px h-6 bg-neutral-300 dark:bg-neutral-600 mx-1" />
|
||||||
|
|
||||||
<MenuButton
|
<MenuButton
|
||||||
onClick={() => setShowLinkDialog(true)}
|
onClick={() => setShowLinkDialog(true)}
|
||||||
@@ -372,7 +362,7 @@ export const CMSEditor: React.FC<CMSEditorProps> = ({ content, onChange, onSave,
|
|||||||
<Minus className="w-4 h-4" />
|
<Minus className="w-4 h-4" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
|
|
||||||
<div className="w-px h-6 bg-neutral-300 mx-1" />
|
<div className="w-px h-6 bg-neutral-300 dark:bg-neutral-600 mx-1" />
|
||||||
|
|
||||||
<MenuButton
|
<MenuButton
|
||||||
onClick={() => editor.chain().focus().setTextAlign('left').run()}
|
onClick={() => editor.chain().focus().setTextAlign('left').run()}
|
||||||
@@ -406,7 +396,7 @@ export const CMSEditor: React.FC<CMSEditorProps> = ({ content, onChange, onSave,
|
|||||||
<AlignJustify className="w-4 h-4" />
|
<AlignJustify className="w-4 h-4" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
|
|
||||||
<div className="w-px h-6 bg-neutral-300 mx-1" />
|
<div className="w-px h-6 bg-neutral-300 dark:bg-neutral-600 mx-1" />
|
||||||
|
|
||||||
<MenuButton
|
<MenuButton
|
||||||
onClick={() => editor.chain().focus().clearNodes().unsetAllMarks().run()}
|
onClick={() => editor.chain().focus().clearNodes().unsetAllMarks().run()}
|
||||||
@@ -415,7 +405,7 @@ export const CMSEditor: React.FC<CMSEditorProps> = ({ content, onChange, onSave,
|
|||||||
<RemoveFormatting className="w-4 h-4" />
|
<RemoveFormatting className="w-4 h-4" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
|
|
||||||
<div className="w-px h-6 bg-neutral-300 mx-1" />
|
<div className="w-px h-6 bg-neutral-300 dark:bg-neutral-600 mx-1" />
|
||||||
|
|
||||||
<MenuButton
|
<MenuButton
|
||||||
onClick={() => editor.chain().focus().undo().run()}
|
onClick={() => editor.chain().focus().undo().run()}
|
||||||
@@ -438,14 +428,14 @@ export const CMSEditor: React.FC<CMSEditorProps> = ({ content, onChange, onSave,
|
|||||||
|
|
||||||
{/* Link Dialog */}
|
{/* Link Dialog */}
|
||||||
{showLinkDialog && (
|
{showLinkDialog && (
|
||||||
<div className="p-3 bg-primary-50 border-b border-primary-200 flex items-center gap-2">
|
<div className="p-3 bg-primary-50 dark:bg-primary-900/30 border-b border-primary-200 dark:border-primary-800 flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
type="url"
|
type="url"
|
||||||
value={linkUrl}
|
value={linkUrl}
|
||||||
onChange={(e) => setLinkUrl(e.target.value)}
|
onChange={(e) => setLinkUrl(e.target.value)}
|
||||||
onKeyPress={(e) => e.key === 'Enter' && addLink()}
|
onKeyPress={(e) => e.key === 'Enter' && addLink()}
|
||||||
placeholder="Enter URL..."
|
placeholder="Enter URL..."
|
||||||
className="flex-1 px-3 py-1 border border-primary-300 rounded-md focus:ring-2 focus:ring-primary-500"
|
className="flex-1 px-3 py-1 border border-primary-300 dark:border-primary-700 bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100 rounded-md focus:ring-2 focus:ring-primary-500"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<Button size="sm" onClick={addLink}>Add Link</Button>
|
<Button size="sm" onClick={addLink}>Add Link</Button>
|
||||||
@@ -460,21 +450,22 @@ export const CMSEditor: React.FC<CMSEditorProps> = ({ content, onChange, onSave,
|
|||||||
|
|
||||||
{/* Editor Content Area */}
|
{/* Editor Content Area */}
|
||||||
<div className="flex-1 flex overflow-hidden">
|
<div className="flex-1 flex overflow-hidden">
|
||||||
{/* Editor */}
|
{/* Editor — prose-invert in dark mode flips the prose typography
|
||||||
|
palette without us having to override every prose-* class. */}
|
||||||
{viewMode !== 'preview' && (
|
{viewMode !== 'preview' && (
|
||||||
<div className={`${viewMode === 'split' ? 'w-1/2 border-r border-neutral-200' : 'w-full'} overflow-auto`}>
|
<div className={`${viewMode === 'split' ? 'w-1/2 border-r border-neutral-200 dark:border-neutral-700' : 'w-full'} overflow-auto bg-white dark:bg-neutral-900`}>
|
||||||
<EditorContent
|
<EditorContent
|
||||||
editor={editor}
|
editor={editor}
|
||||||
className="min-h-[400px] p-4 prose prose-neutral max-w-none focus:outline-none [&_.ProseMirror]:min-h-[400px] [&_.ProseMirror]:outline-none [&_.ProseMirror_p.is-editor-empty:first-child::before]:content-[attr(data-placeholder)] [&_.ProseMirror_p.is-editor-empty:first-child::before]:text-neutral-400 [&_.ProseMirror_p.is-editor-empty:first-child::before]:pointer-events-none [&_.ProseMirror_p.is-editor-empty:first-child::before]:float-left [&_.ProseMirror_p.is-editor-empty:first-child::before]:h-0 [&_.ProseMirror_br.hard-break]:display-block [&_.ProseMirror_br.hard-break]:content-[''] [&_.ProseMirror_br.hard-break]:margin-[0.5em_0] [&_.ProseMirror_pre]:bg-neutral-100 [&_.ProseMirror_pre]:rounded-md [&_.ProseMirror_pre]:p-4 [&_.ProseMirror_pre]:overflow-x-auto [&_.ProseMirror_code]:bg-neutral-100 [&_.ProseMirror_code]:rounded [&_.ProseMirror_code]:px-1 [&_.ProseMirror_code]:py-0.5 [&_.ProseMirror_code]:text-sm [&_.ProseMirror_pre_code]:bg-transparent [&_.ProseMirror_pre_code]:p-0"
|
className="min-h-[400px] p-4 prose prose-neutral dark:prose-invert max-w-none focus:outline-none [&_.ProseMirror]:min-h-[400px] [&_.ProseMirror]:outline-none [&_.ProseMirror]:text-neutral-900 dark:[&_.ProseMirror]:text-neutral-100 [&_.ProseMirror_p.is-editor-empty:first-child::before]:content-[attr(data-placeholder)] [&_.ProseMirror_p.is-editor-empty:first-child::before]:text-neutral-400 dark:[&_.ProseMirror_p.is-editor-empty:first-child::before]:text-neutral-500 [&_.ProseMirror_p.is-editor-empty:first-child::before]:pointer-events-none [&_.ProseMirror_p.is-editor-empty:first-child::before]:float-left [&_.ProseMirror_p.is-editor-empty:first-child::before]:h-0 [&_.ProseMirror_br.hard-break]:display-block [&_.ProseMirror_br.hard-break]:content-[''] [&_.ProseMirror_br.hard-break]:margin-[0.5em_0] [&_.ProseMirror_pre]:bg-neutral-100 dark:[&_.ProseMirror_pre]:bg-neutral-800 [&_.ProseMirror_pre]:rounded-md [&_.ProseMirror_pre]:p-4 [&_.ProseMirror_pre]:overflow-x-auto [&_.ProseMirror_code]:bg-neutral-100 dark:[&_.ProseMirror_code]:bg-neutral-800 [&_.ProseMirror_code]:rounded [&_.ProseMirror_code]:px-1 [&_.ProseMirror_code]:py-0.5 [&_.ProseMirror_code]:text-sm [&_.ProseMirror_pre_code]:bg-transparent [&_.ProseMirror_pre_code]:p-0"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Preview */}
|
{/* Preview */}
|
||||||
{viewMode !== 'edit' && (
|
{viewMode !== 'edit' && (
|
||||||
<div className={`${viewMode === 'split' ? 'w-1/2' : 'w-full'} overflow-auto bg-neutral-50 p-4`}>
|
<div className={`${viewMode === 'split' ? 'w-1/2' : 'w-full'} overflow-auto bg-neutral-50 dark:bg-neutral-800 p-4`}>
|
||||||
<div
|
<div
|
||||||
className="prose prose-neutral max-w-none"
|
className="prose prose-neutral dark:prose-invert max-w-none"
|
||||||
dangerouslySetInnerHTML={{ __html: getPreviewContent() }}
|
dangerouslySetInnerHTML={{ __html: getPreviewContent() }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -482,12 +473,12 @@ export const CMSEditor: React.FC<CMSEditorProps> = ({ content, onChange, onSave,
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status Bar */}
|
{/* Status Bar */}
|
||||||
<div className="flex items-center justify-between px-4 py-2 bg-neutral-50 border-t border-neutral-200 text-sm text-neutral-600">
|
<div className="flex items-center justify-between px-4 py-2 bg-neutral-50 dark:bg-neutral-800 border-t border-neutral-200 dark:border-neutral-700 text-sm text-neutral-600 dark:text-neutral-300">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<span>{wordCount} words</span>
|
<span>{wordCount} words</span>
|
||||||
<span>{charCount} characters</span>
|
<span>{charCount} characters</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-neutral-500">
|
<div className="text-xs text-neutral-500 dark:text-neutral-400">
|
||||||
Press Shift+Enter for line break, Enter for new paragraph
|
Press Shift+Enter for line break, Enter for new paragraph
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -496,7 +487,7 @@ export const CMSEditor: React.FC<CMSEditorProps> = ({ content, onChange, onSave,
|
|||||||
{/* Help Modal */}
|
{/* Help Modal */}
|
||||||
{showHelp && (
|
{showHelp && (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||||
<div className="bg-white rounded-lg max-w-2xl w-full max-h-[80vh] overflow-auto">
|
<div className="bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100 rounded-lg max-w-2xl w-full max-h-[80vh] overflow-auto">
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<h2 className="text-xl font-semibold mb-4">Editor Help & Keyboard Shortcuts</h2>
|
<h2 className="text-xl font-semibold mb-4">Editor Help & Keyboard Shortcuts</h2>
|
||||||
|
|
||||||
|
|||||||
@@ -158,11 +158,11 @@ export const EventRenameDialog: React.FC<EventRenameDialogProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{renameResult.newShareLink && (
|
{renameResult.newShareLink && (
|
||||||
<div className="p-3 bg-neutral-50 rounded-lg">
|
<div className="p-3 bg-neutral-50 dark:bg-neutral-800 rounded-lg">
|
||||||
<p className="text-sm font-medium text-neutral-700 mb-1">
|
<p className="text-sm font-medium text-neutral-700 dark:text-neutral-200 mb-1">
|
||||||
{t('events.rename.newLink', 'New Gallery Link')}
|
{t('events.rename.newLink', 'New Gallery Link')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-neutral-900 break-all">{renameResult.newShareLink}</p>
|
<p className="text-sm text-neutral-900 dark:text-neutral-100 break-all">{renameResult.newShareLink}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -94,28 +94,28 @@ export const FeedbackModerationPanel: React.FC<FeedbackModerationPanelProps> = (
|
|||||||
{!hasPending ? (
|
{!hasPending ? (
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
<CheckCircle className="w-12 h-12 text-green-500 mx-auto mb-3" />
|
<CheckCircle className="w-12 h-12 text-green-500 mx-auto mb-3" />
|
||||||
<p className="text-neutral-600">{t('feedback.noPendingComments', 'No comments pending moderation')}</p>
|
<p className="text-neutral-600 dark:text-neutral-300">{t('feedback.noPendingComments', 'No comments pending moderation')}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{pendingComments.slice(0, showAll ? undefined : maxItems).map((item) => (
|
{pendingComments.slice(0, showAll ? undefined : maxItems).map((item) => (
|
||||||
<div key={item.id} className="border border-neutral-200 rounded-lg p-4 hover:bg-neutral-50">
|
<div key={item.id} className="border border-neutral-200 dark:border-neutral-700 rounded-lg p-4 hover:bg-neutral-50 dark:hover:bg-neutral-800">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<div className="w-10 h-10 bg-neutral-100 rounded-full flex items-center justify-center">
|
<div className="w-10 h-10 bg-neutral-100 dark:bg-neutral-800 rounded-full flex items-center justify-center">
|
||||||
<User className="w-5 h-5 text-neutral-600" />
|
<User className="w-5 h-5 text-neutral-600 dark:text-neutral-300" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<span className="font-medium text-neutral-900">
|
<span className="font-medium text-neutral-900 dark:text-neutral-100">
|
||||||
{item.guest_name || t('feedback.anonymous', 'Anonymous')}
|
{item.guest_name || t('feedback.anonymous', 'Anonymous')}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-neutral-500">•</span>
|
<span className="text-neutral-500 dark:text-neutral-400">•</span>
|
||||||
<span className="text-neutral-500">
|
<span className="text-neutral-500 dark:text-neutral-400">
|
||||||
{format(
|
{format(
|
||||||
typeof item.created_at === 'string'
|
typeof item.created_at === 'string'
|
||||||
? parseISO(item.created_at)
|
? parseISO(item.created_at)
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export const MandatoryPasswordChangeModal: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowPasswords(prev => ({ ...prev, current: !prev.current }))}
|
onClick={() => setShowPasswords(prev => ({ ...prev, current: !prev.current }))}
|
||||||
className="absolute right-3 top-2 p-1 hover:bg-neutral-100 rounded"
|
className="absolute right-3 top-2 p-1 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded"
|
||||||
>
|
>
|
||||||
{showPasswords.current ?
|
{showPasswords.current ?
|
||||||
<EyeOff className="w-4 h-4 text-neutral-500" /> :
|
<EyeOff className="w-4 h-4 text-neutral-500" /> :
|
||||||
@@ -163,7 +163,7 @@ export const MandatoryPasswordChangeModal: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowPasswords(prev => ({ ...prev, new: !prev.new }))}
|
onClick={() => setShowPasswords(prev => ({ ...prev, new: !prev.new }))}
|
||||||
className="absolute right-3 top-2 p-1 hover:bg-neutral-100 rounded"
|
className="absolute right-3 top-2 p-1 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded"
|
||||||
>
|
>
|
||||||
{showPasswords.new ?
|
{showPasswords.new ?
|
||||||
<EyeOff className="w-4 h-4 text-neutral-500" /> :
|
<EyeOff className="w-4 h-4 text-neutral-500" /> :
|
||||||
@@ -191,7 +191,7 @@ export const MandatoryPasswordChangeModal: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowPasswords(prev => ({ ...prev, confirm: !prev.confirm }))}
|
onClick={() => setShowPasswords(prev => ({ ...prev, confirm: !prev.confirm }))}
|
||||||
className="absolute right-3 top-2 p-1 hover:bg-neutral-100 rounded"
|
className="absolute right-3 top-2 p-1 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded"
|
||||||
>
|
>
|
||||||
{showPasswords.confirm ?
|
{showPasswords.confirm ?
|
||||||
<EyeOff className="w-4 h-4 text-neutral-500" /> :
|
<EyeOff className="w-4 h-4 text-neutral-500" /> :
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ export const PasswordResetModal: React.FC<PasswordResetModalProps> = ({
|
|||||||
type="text"
|
type="text"
|
||||||
value={resultPassword}
|
value={resultPassword}
|
||||||
readOnly
|
readOnly
|
||||||
className="flex-1 px-3 py-2 bg-neutral-50 border border-neutral-300 rounded-lg font-mono text-sm"
|
className="flex-1 px-3 py-2 bg-neutral-50 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 text-neutral-900 dark:text-neutral-100 rounded-lg font-mono text-sm"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -50,7 +50,10 @@ export const CMSContentBlock: React.FC<CMSContentBlockProps> = ({ slug, fallback
|
|||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-neutral-50 flex items-center justify-center">
|
<div
|
||||||
|
className="min-h-screen flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: 'var(--color-background, #fafafa)' }}
|
||||||
|
>
|
||||||
<Loading size="lg" />
|
<Loading size="lg" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -80,11 +83,20 @@ export const CMSContentBlock: React.FC<CMSContentBlockProps> = ({ slug, fallback
|
|||||||
<main className="flex-1 flex items-start justify-center px-4">
|
<main className="flex-1 flex items-start justify-center px-4">
|
||||||
<div className="max-w-2xl w-full">
|
<div className="max-w-2xl w-full">
|
||||||
<Card padding="lg">
|
<Card padding="lg">
|
||||||
<h1 className="text-2xl sm:text-3xl font-bold text-neutral-900 dark:text-neutral-100 mb-6">
|
{/*
|
||||||
|
* Heading + body now read from theme tokens so dark themes
|
||||||
|
* (and force-dark mode) render correctly without dark: variants
|
||||||
|
* fighting the CSS variables.
|
||||||
|
*/}
|
||||||
|
<h1
|
||||||
|
className="text-2xl sm:text-3xl font-bold mb-6"
|
||||||
|
style={{ color: 'var(--color-text)' }}
|
||||||
|
>
|
||||||
{page.title}
|
{page.title}
|
||||||
</h1>
|
</h1>
|
||||||
<div
|
<div
|
||||||
className="prose prose-neutral dark:prose-invert max-w-none"
|
className="prose prose-neutral dark:prose-invert max-w-none"
|
||||||
|
style={{ color: 'var(--color-text)' }}
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: DOMPurify.sanitize(page.content, {
|
__html: DOMPurify.sanitize(page.content, {
|
||||||
ALLOWED_TAGS,
|
ALLOWED_TAGS,
|
||||||
@@ -97,7 +109,8 @@ export const CMSContentBlock: React.FC<CMSContentBlockProps> = ({ slug, fallback
|
|||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<Link
|
<Link
|
||||||
to="/"
|
to="/"
|
||||||
className="text-sm font-medium text-primary-600 hover:text-primary-700"
|
className="text-sm font-medium hover:underline"
|
||||||
|
style={{ color: 'var(--color-accent)' }}
|
||||||
>
|
>
|
||||||
{lang === 'de' ? '← Zur Startseite' : '← Back to home'}
|
{lang === 'de' ? '← Zur Startseite' : '← Back to home'}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -106,13 +119,16 @@ export const CMSContentBlock: React.FC<CMSContentBlockProps> = ({ slug, fallback
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer className="py-8 text-center text-xs text-neutral-500">
|
<footer
|
||||||
|
className="py-8 text-center text-xs"
|
||||||
|
style={{ color: 'var(--color-muted-text)' }}
|
||||||
|
>
|
||||||
<div className="flex justify-center gap-4">
|
<div className="flex justify-center gap-4">
|
||||||
<Link to="/impressum" className="hover:text-neutral-700">
|
<Link to="/impressum" className="hover:underline">
|
||||||
{lang === 'de' ? 'Impressum' : 'Legal Notice'}
|
{lang === 'de' ? 'Impressum' : 'Legal Notice'}
|
||||||
</Link>
|
</Link>
|
||||||
<span className="text-neutral-400">•</span>
|
<span style={{ color: 'var(--color-surface-border)' }}>•</span>
|
||||||
<Link to="/datenschutz" className="hover:text-neutral-700">
|
<Link to="/datenschutz" className="hover:underline">
|
||||||
{lang === 'de' ? 'Datenschutz' : 'Privacy Policy'}
|
{lang === 'de' ? 'Datenschutz' : 'Privacy Policy'}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -487,7 +487,7 @@ export const CMSPage: React.FC = () => {
|
|||||||
<span className="text-xs text-neutral-500 dark:text-neutral-400">{t('settings.publicSite.previewSandboxed')}</span>
|
<span className="text-xs text-neutral-500 dark:text-neutral-400">{t('settings.publicSite.previewSandboxed')}</span>
|
||||||
</div>
|
</div>
|
||||||
{publicSiteEnabled ? (
|
{publicSiteEnabled ? (
|
||||||
<div className="rounded-xl border border-neutral-200 overflow-hidden shadow-sm bg-white">
|
<div className="rounded-xl border border-neutral-200 dark:border-neutral-700 overflow-hidden shadow-sm bg-white dark:bg-neutral-800">
|
||||||
<iframe
|
<iframe
|
||||||
title="public-site-preview"
|
title="public-site-preview"
|
||||||
sandbox="allow-same-origin"
|
sandbox="allow-same-origin"
|
||||||
@@ -576,7 +576,7 @@ export const CMSPage: React.FC = () => {
|
|||||||
<Card padding="md" className="mt-4">
|
<Card padding="md" className="mt-4">
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
{isAutoSaving && (
|
{isAutoSaving && (
|
||||||
<div className="flex items-center gap-2 text-neutral-600">
|
<div className="flex items-center gap-2 text-neutral-600 dark:text-neutral-300">
|
||||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
|
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
|
||||||
Auto-saving...
|
Auto-saving...
|
||||||
</div>
|
</div>
|
||||||
@@ -637,7 +637,7 @@ export const CMSPage: React.FC = () => {
|
|||||||
<label className="flex items-start gap-3 cursor-pointer">
|
<label className="flex items-start gap-3 cursor-pointer">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="mt-1 h-4 w-4 rounded border-neutral-300 text-primary-600 focus:ring-primary-500"
|
className="mt-1 h-4 w-4 rounded border-neutral-300 dark:border-neutral-600 text-primary-600 focus:ring-primary-500"
|
||||||
checked={!!editForm.use_external_url}
|
checked={!!editForm.use_external_url}
|
||||||
onChange={(e) => handleUseExternalUrlChange(e.target.checked)}
|
onChange={(e) => handleUseExternalUrlChange(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -27,9 +27,14 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Code block styling */
|
/*
|
||||||
|
* Code block styling — uses the 8-token CI palette so blocks read against
|
||||||
|
* both light and dark surfaces. The variables fall back to the previous
|
||||||
|
* #f5f5f5 if a host page hasn't loaded the theme yet.
|
||||||
|
*/
|
||||||
.prose pre {
|
.prose pre {
|
||||||
background-color: #f5f5f5;
|
background-color: var(--color-elevated, #f5f5f5);
|
||||||
|
border: 1px solid var(--color-surface-border, #e5e5e5);
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
@@ -45,13 +50,20 @@
|
|||||||
|
|
||||||
/* Inline code styling */
|
/* Inline code styling */
|
||||||
.prose code {
|
.prose code {
|
||||||
background-color: #f5f5f5;
|
background-color: var(--color-elevated, #f5f5f5);
|
||||||
padding: 0.125rem 0.375rem;
|
padding: 0.125rem 0.375rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
font-size: 0.875em;
|
font-size: 0.875em;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Blockquote styling — themed left rule that flips with dark mode. */
|
||||||
|
.prose blockquote {
|
||||||
|
border-left: 3px solid var(--color-accent, #017C7C);
|
||||||
|
padding-left: 1rem;
|
||||||
|
color: var(--color-muted-text, #737373);
|
||||||
|
}
|
||||||
|
|
||||||
/* Text alignment classes */
|
/* Text alignment classes */
|
||||||
.prose .text-left {
|
.prose .text-left {
|
||||||
text-align: left !important;
|
text-align: left !important;
|
||||||
|
|||||||
Reference in New Issue
Block a user