diff --git a/.env.example b/.env.example index 15c04a1..d9c2521 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,7 @@ PROJECT_NAME=jama # Image version to run (set by build.sh, or use 'latest') -JAMA_VERSION=0.9.14 +JAMA_VERSION=0.9.15 # App port — the host port Docker maps to the container PORT=3000 diff --git a/backend/package.json b/backend/package.json index 8fd063f..6995f75 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "jama-backend", - "version": "0.9.14", + "version": "0.9.15", "description": "TeamChat backend server", "main": "src/index.js", "scripts": { diff --git a/backend/src/models/db.js b/backend/src/models/db.js index 6f3a37c..ecac3f0 100644 --- a/backend/src/models/db.js +++ b/backend/src/models/db.js @@ -169,6 +169,9 @@ function initDb() { insertSetting.run('icon_groupinfo', ''); insertSetting.run('pwa_icon_192', ''); insertSetting.run('pwa_icon_512', ''); + insertSetting.run('color_title', ''); + insertSetting.run('color_avatar_public', ''); + insertSetting.run('color_avatar_dm', ''); // Migration: add hide_admin_tag if upgrading from older version try { diff --git a/backend/src/routes/settings.js b/backend/src/routes/settings.js index c8749a0..c081b05 100644 --- a/backend/src/routes/settings.js +++ b/backend/src/routes/settings.js @@ -114,12 +114,22 @@ router.post('/icon-groupinfo', authMiddleware, adminMiddleware, uploadGroupInfo. }); // Reset all settings to defaults (admin) +router.patch('/colors', authMiddleware, adminMiddleware, (req, res) => { + const { colorTitle, colorAvatarPublic, colorAvatarDm } = req.body; + const db = getDb(); + const upd = db.prepare("INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')"); + if (colorTitle !== undefined) upd.run('color_title', colorTitle || '', colorTitle || ''); + if (colorAvatarPublic !== undefined) upd.run('color_avatar_public', colorAvatarPublic || '', colorAvatarPublic || ''); + if (colorAvatarDm !== undefined) upd.run('color_avatar_dm', colorAvatarDm || '', colorAvatarDm || ''); + res.json({ success: true }); +}); + router.post('/reset', authMiddleware, adminMiddleware, (req, res) => { const db = getDb(); const originalName = process.env.APP_NAME || 'jama'; db.prepare("UPDATE settings SET value = ?, updated_at = datetime('now') WHERE key = 'app_name'").run(originalName); db.prepare("UPDATE settings SET value = '', updated_at = datetime('now') WHERE key = 'logo_url'").run(); - db.prepare("UPDATE settings SET value = '', updated_at = datetime('now') WHERE key IN ('icon_newchat', 'icon_groupinfo', 'pwa_icon_192', 'pwa_icon_512')").run(); + db.prepare("UPDATE settings SET value = '', updated_at = datetime('now') WHERE key IN ('icon_newchat', 'icon_groupinfo', 'pwa_icon_192', 'pwa_icon_512', 'color_title', 'color_avatar_public', 'color_avatar_dm')").run(); res.json({ success: true }); }); diff --git a/build.sh b/build.sh index bc90722..f7fe532 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.9.14}" +VERSION="${1:-0.9.15}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="jama" diff --git a/frontend/package.json b/frontend/package.json index 9165d47..5d9313f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jama-frontend", - "version": "0.9.14", + "version": "0.9.15", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/components/BrandingModal.jsx b/frontend/src/components/BrandingModal.jsx index 63436d5..caee937 100644 --- a/frontend/src/components/BrandingModal.jsx +++ b/frontend/src/components/BrandingModal.jsx @@ -2,22 +2,50 @@ import { useState, useEffect } from 'react'; import { api } from '../utils/api.js'; import { useToast } from '../contexts/ToastContext.jsx'; +const DEFAULT_TITLE_COLOR = '#1a73e8'; +const DEFAULT_PUBLIC_COLOR = '#1a73e8'; +const DEFAULT_DM_COLOR = '#a142f4'; + +function ColorSwatch({ color, title }) { + return ( +
+ ); +} + export default function BrandingModal({ onClose }) { const toast = useToast(); + const [tab, setTab] = useState('general'); // 'general' | 'colors' const [settings, setSettings] = useState({}); const [appName, setAppName] = useState(''); const [loading, setLoading] = useState(false); const [resetting, setResetting] = useState(false); const [showResetConfirm, setShowResetConfirm] = useState(false); + // Color state + const [colorTitle, setColorTitle] = useState(DEFAULT_TITLE_COLOR); + const [colorPublic, setColorPublic] = useState(DEFAULT_PUBLIC_COLOR); + const [colorDm, setColorDm] = useState(DEFAULT_DM_COLOR); + const [savingColors, setSavingColors] = useState(false); + useEffect(() => { api.getSettings().then(({ settings }) => { setSettings(settings); setAppName(settings.app_name || 'jama'); + setColorTitle(settings.color_title || DEFAULT_TITLE_COLOR); + setColorPublic(settings.color_avatar_public || DEFAULT_PUBLIC_COLOR); + setColorDm(settings.color_avatar_dm || DEFAULT_DM_COLOR); }).catch(() => {}); }, []); - const notifySidebarRefresh = () => window.dispatchEvent(new Event('jama:settings-changed')); + const notifySidebarRefresh = () => { + window.dispatchEvent(new Event('jama:settings-changed')); + }; const handleSaveName = async () => { if (!appName.trim()) return; @@ -48,6 +76,29 @@ export default function BrandingModal({ onClose }) { } }; + const handleSaveColors = async () => { + setSavingColors(true); + try { + await api.updateColors({ + colorTitle, + colorAvatarPublic: colorPublic, + colorAvatarDm: colorDm, + }); + setSettings(prev => ({ + ...prev, + color_title: colorTitle, + color_avatar_public: colorPublic, + color_avatar_dm: colorDm, + })); + toast('Colors updated', 'success'); + notifySidebarRefresh(); + } catch (e) { + toast(e.message, 'error'); + } finally { + setSavingColors(false); + } + }; + const handleReset = async () => { setResetting(true); try { @@ -55,6 +106,9 @@ export default function BrandingModal({ onClose }) { const { settings: fresh } = await api.getSettings(); setSettings(fresh); setAppName(fresh.app_name || 'jama'); + setColorTitle(DEFAULT_TITLE_COLOR); + setColorPublic(DEFAULT_PUBLIC_COLOR); + setColorDm(DEFAULT_DM_COLOR); toast('Settings reset to defaults', 'success'); notifySidebarRefresh(); setShowResetConfirm(false); @@ -75,80 +129,163 @@ export default function BrandingModal({ onClose }) { - {/* App Logo */} -- Square format, max 1MB. Used in sidebar, login page and browser tab. -
-- This will reset the app name and logo to their install defaults. This cannot be undone. -
-+ Square format, max 1MB. Used in sidebar, login page and browser tab. +
+ This will reset the app name, logo and all colours to their install defaults. This cannot be undone. +
++ The color of the app name shown in the top bar. +
++ Background color for public channel avatars (users without a custom avatar). +
++ Background color for private group and direct message avatars (users without a custom avatar). +
+