Add default theme management to branding settings and admin interface
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m14s

This commit is contained in:
2026-02-27 15:54:41 +01:00
parent d7d7991ff0
commit 2762df3e57
6 changed files with 102 additions and 8 deletions

View File

@@ -3,18 +3,19 @@ import { useNavigate } from 'react-router-dom';
import {
Users, Shield, Search, Trash2, ChevronDown, Loader2,
MoreVertical, Key, UserCheck, UserX, UserPlus, Mail, Lock, User,
Upload, X as XIcon, Image, Type,
Upload, X as XIcon, Image, Type, Palette,
} from 'lucide-react';
import { useAuth } from '../contexts/AuthContext';
import { useLanguage } from '../contexts/LanguageContext';
import { useBranding } from '../contexts/BrandingContext';
import { themes } from '../themes';
import api from '../services/api';
import toast from 'react-hot-toast';
export default function Admin() {
const { user } = useAuth();
const { t, language } = useLanguage();
const { appName, hasLogo, logoUrl, refreshBranding } = useBranding();
const { appName, hasLogo, logoUrl, defaultTheme, refreshBranding } = useBranding();
const navigate = useNavigate();
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
@@ -31,6 +32,8 @@ export default function Admin() {
const [savingName, setSavingName] = useState(false);
const [uploadingLogo, setUploadingLogo] = useState(false);
const logoInputRef = useRef(null);
const [editDefaultTheme, setEditDefaultTheme] = useState('');
const [savingDefaultTheme, setSavingDefaultTheme] = useState(false);
useEffect(() => {
if (user?.role !== 'admin') {
@@ -44,6 +47,10 @@ export default function Admin() {
setEditAppName(appName || 'Redlight');
}, [appName]);
useEffect(() => {
setEditDefaultTheme(defaultTheme || 'dark');
}, [defaultTheme]);
const fetchUsers = async () => {
try {
const res = await api.get('/admin/users');
@@ -135,6 +142,20 @@ export default function Admin() {
}
};
const handleDefaultThemeSave = async () => {
if (!editDefaultTheme) return;
setSavingDefaultTheme(true);
try {
await api.put('/branding/default-theme', { defaultTheme: editDefaultTheme });
toast.success(t('admin.defaultThemeSaved'));
refreshBranding();
} catch {
toast.error(t('admin.defaultThemeUpdateFailed'));
} finally {
setSavingDefaultTheme(false);
}
};
const handleCreateUser = async (e) => {
e.preventDefault();
setCreatingUser(true);
@@ -266,6 +287,35 @@ export default function Admin() {
</div>
</div>
</div>
{/* Default theme */}
<div className="mt-6 pt-6 border-t border-th-border">
<div className="flex items-center gap-2 mb-1">
<Palette size={16} className="text-th-accent" />
<label className="block text-sm font-medium text-th-text">{t('admin.defaultThemeLabel')}</label>
</div>
<p className="text-xs text-th-text-s mb-3">{t('admin.defaultThemeDesc')}</p>
<div className="flex items-center gap-2">
<select
value={editDefaultTheme}
onChange={e => setEditDefaultTheme(e.target.value)}
className="input-field text-sm flex-1"
>
{themes.map(th => (
<option key={th.id} value={th.id}>
{th.name} ({th.type === 'light' ? t('themes.light') : t('themes.dark')})
</option>
))}
</select>
<button
onClick={handleDefaultThemeSave}
disabled={savingDefaultTheme || editDefaultTheme === (defaultTheme || 'dark')}
className="btn-primary text-sm px-4 flex-shrink-0"
>
{savingDefaultTheme ? <Loader2 size={14} className="animate-spin" /> : t('common.save')}
</button>
</div>
</div>
</div>
{/* Search */}