ACP: general settings, branding, and favicon uploads
This commit is contained in:
@@ -13,7 +13,7 @@ import Ucp from './pages/Ucp'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { fetchSetting, fetchVersion, getForum, getThread } from './api/client'
|
||||
|
||||
function PortalHeader({ userMenu, isAuthenticated }) {
|
||||
function PortalHeader({ userMenu, isAuthenticated, forumName, logoUrl, showHeaderName }) {
|
||||
const { t } = useTranslation()
|
||||
const location = useLocation()
|
||||
const [crumbs, setCrumbs] = useState([])
|
||||
@@ -110,8 +110,12 @@ function PortalHeader({ userMenu, isAuthenticated }) {
|
||||
<Container className="pt-2 pb-2 bb-portal-shell">
|
||||
<div className="bb-portal-banner">
|
||||
<div className="bb-portal-brand">
|
||||
<div className="bb-portal-logo">24unix.net</div>
|
||||
<div className="bb-portal-tagline">{t('portal.tagline')}</div>
|
||||
{logoUrl && (
|
||||
<img src={logoUrl} alt={forumName || 'Forum'} className="bb-portal-logo-image" />
|
||||
)}
|
||||
{(showHeaderName || !logoUrl) && (
|
||||
<div className="bb-portal-logo">{forumName || '24unix.net'}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="bb-portal-search">
|
||||
<input type="text" placeholder={t('portal.search_placeholder')} disabled />
|
||||
@@ -195,10 +199,27 @@ function AppShell() {
|
||||
const { t } = useTranslation()
|
||||
const { token, email, logout, isAdmin } = useAuth()
|
||||
const [versionInfo, setVersionInfo] = useState(null)
|
||||
const [theme, setTheme] = useState(() => localStorage.getItem('speedbb_theme') || 'auto')
|
||||
const [theme, setTheme] = useState('auto')
|
||||
const [resolvedTheme, setResolvedTheme] = useState('light')
|
||||
const [accentOverride, setAccentOverride] = useState(
|
||||
() => localStorage.getItem('speedbb_accent') || ''
|
||||
)
|
||||
const [settings, setSettings] = useState({
|
||||
forumName: '',
|
||||
defaultTheme: 'auto',
|
||||
accentDark: '',
|
||||
accentLight: '',
|
||||
logoDark: '',
|
||||
logoLight: '',
|
||||
showHeaderName: true,
|
||||
faviconIco: '',
|
||||
favicon16: '',
|
||||
favicon32: '',
|
||||
favicon48: '',
|
||||
favicon64: '',
|
||||
favicon128: '',
|
||||
favicon256: '',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
fetchVersion()
|
||||
@@ -207,18 +228,89 @@ function AppShell() {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetchSetting('accent_color')
|
||||
.then((setting) => {
|
||||
if (setting?.value && !accentOverride) {
|
||||
document.documentElement.style.setProperty('--bb-accent', setting.value)
|
||||
let active = true
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
const [
|
||||
forumNameSetting,
|
||||
defaultThemeSetting,
|
||||
accentDarkSetting,
|
||||
accentLightSetting,
|
||||
logoDarkSetting,
|
||||
logoLightSetting,
|
||||
showHeaderNameSetting,
|
||||
faviconIcoSetting,
|
||||
favicon16Setting,
|
||||
favicon32Setting,
|
||||
favicon48Setting,
|
||||
favicon64Setting,
|
||||
favicon128Setting,
|
||||
favicon256Setting,
|
||||
] = await Promise.all([
|
||||
fetchSetting('forum_name'),
|
||||
fetchSetting('default_theme'),
|
||||
fetchSetting('accent_color_dark'),
|
||||
fetchSetting('accent_color_light'),
|
||||
fetchSetting('logo_dark'),
|
||||
fetchSetting('logo_light'),
|
||||
fetchSetting('show_header_name'),
|
||||
fetchSetting('favicon_ico'),
|
||||
fetchSetting('favicon_16'),
|
||||
fetchSetting('favicon_32'),
|
||||
fetchSetting('favicon_48'),
|
||||
fetchSetting('favicon_64'),
|
||||
fetchSetting('favicon_128'),
|
||||
fetchSetting('favicon_256'),
|
||||
])
|
||||
if (!active) return
|
||||
const next = {
|
||||
forumName: forumNameSetting?.value || '',
|
||||
defaultTheme: defaultThemeSetting?.value || 'auto',
|
||||
accentDark: accentDarkSetting?.value || '',
|
||||
accentLight: accentLightSetting?.value || '',
|
||||
logoDark: logoDarkSetting?.value || '',
|
||||
logoLight: logoLightSetting?.value || '',
|
||||
showHeaderName: showHeaderNameSetting?.value !== 'false',
|
||||
faviconIco: faviconIcoSetting?.value || '',
|
||||
favicon16: favicon16Setting?.value || '',
|
||||
favicon32: favicon32Setting?.value || '',
|
||||
favicon48: favicon48Setting?.value || '',
|
||||
favicon64: favicon64Setting?.value || '',
|
||||
favicon128: favicon128Setting?.value || '',
|
||||
favicon256: favicon256Setting?.value || '',
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}, [accentOverride])
|
||||
setSettings(next)
|
||||
} catch {
|
||||
// keep defaults
|
||||
}
|
||||
}
|
||||
loadSettings()
|
||||
return () => {
|
||||
active = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const stored = token ? localStorage.getItem('speedbb_theme') : null
|
||||
const nextTheme = stored || settings.defaultTheme || 'auto'
|
||||
setTheme(nextTheme)
|
||||
}, [token, settings.defaultTheme])
|
||||
|
||||
useEffect(() => {
|
||||
const handleSettingsUpdate = (event) => {
|
||||
const next = event.detail
|
||||
if (!next) return
|
||||
setSettings((prev) => ({ ...prev, ...next }))
|
||||
}
|
||||
|
||||
window.addEventListener('speedbb-settings-updated', handleSettingsUpdate)
|
||||
return () => {
|
||||
window.removeEventListener('speedbb-settings-updated', handleSettingsUpdate)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (accentOverride) {
|
||||
document.documentElement.style.setProperty('--bb-accent', accentOverride)
|
||||
localStorage.setItem('speedbb_accent', accentOverride)
|
||||
} else {
|
||||
localStorage.removeItem('speedbb_accent')
|
||||
@@ -231,9 +323,12 @@ function AppShell() {
|
||||
|
||||
const applyTheme = (mode) => {
|
||||
if (mode === 'auto') {
|
||||
root.setAttribute('data-bs-theme', media.matches ? 'dark' : 'light')
|
||||
const next = media.matches ? 'dark' : 'light'
|
||||
root.setAttribute('data-bs-theme', next)
|
||||
setResolvedTheme(next)
|
||||
} else {
|
||||
root.setAttribute('data-bs-theme', mode)
|
||||
setResolvedTheme(mode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,10 +347,76 @@ function AppShell() {
|
||||
}
|
||||
}, [theme])
|
||||
|
||||
useEffect(() => {
|
||||
const accent =
|
||||
accentOverride ||
|
||||
(resolvedTheme === 'dark' ? settings.accentDark : settings.accentLight) ||
|
||||
settings.accentDark ||
|
||||
settings.accentLight
|
||||
if (accent) {
|
||||
document.documentElement.style.setProperty('--bb-accent', accent)
|
||||
}
|
||||
}, [accentOverride, resolvedTheme, settings])
|
||||
|
||||
useEffect(() => {
|
||||
if (settings.forumName) {
|
||||
document.title = settings.forumName
|
||||
}
|
||||
}, [settings.forumName])
|
||||
|
||||
useEffect(() => {
|
||||
const upsertIcon = (id, rel, href, sizes, type) => {
|
||||
if (!href) {
|
||||
const existing = document.getElementById(id)
|
||||
if (existing) {
|
||||
existing.remove()
|
||||
}
|
||||
return
|
||||
}
|
||||
let link = document.getElementById(id)
|
||||
if (!link) {
|
||||
link = document.createElement('link')
|
||||
link.id = id
|
||||
document.head.appendChild(link)
|
||||
}
|
||||
link.setAttribute('rel', rel)
|
||||
link.setAttribute('href', href)
|
||||
if (sizes) {
|
||||
link.setAttribute('sizes', sizes)
|
||||
} else {
|
||||
link.removeAttribute('sizes')
|
||||
}
|
||||
if (type) {
|
||||
link.setAttribute('type', type)
|
||||
} else {
|
||||
link.removeAttribute('type')
|
||||
}
|
||||
}
|
||||
|
||||
upsertIcon('favicon-ico', 'icon', settings.faviconIco, null, 'image/x-icon')
|
||||
upsertIcon('favicon-16', 'icon', settings.favicon16, '16x16', 'image/png')
|
||||
upsertIcon('favicon-32', 'icon', settings.favicon32, '32x32', 'image/png')
|
||||
upsertIcon('favicon-48', 'icon', settings.favicon48, '48x48', 'image/png')
|
||||
upsertIcon('favicon-64', 'icon', settings.favicon64, '64x64', 'image/png')
|
||||
upsertIcon('favicon-128', 'icon', settings.favicon128, '128x128', 'image/png')
|
||||
upsertIcon('favicon-256', 'icon', settings.favicon256, '256x256', 'image/png')
|
||||
}, [
|
||||
settings.faviconIco,
|
||||
settings.favicon16,
|
||||
settings.favicon32,
|
||||
settings.favicon48,
|
||||
settings.favicon64,
|
||||
settings.favicon128,
|
||||
settings.favicon256,
|
||||
])
|
||||
|
||||
return (
|
||||
<div className="bb-shell">
|
||||
<PortalHeader
|
||||
isAuthenticated={!!token}
|
||||
forumName={settings.forumName}
|
||||
logoUrl={resolvedTheme === 'dark' ? settings.logoDark : settings.logoLight}
|
||||
showHeaderName={settings.showHeaderName}
|
||||
userMenu={
|
||||
token ? (
|
||||
<NavDropdown
|
||||
|
||||
Reference in New Issue
Block a user