mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-29 08:04:53 +00:00
feat(branding): white-label — public /branding, admin write, shell theming (#41)
app_settings branding.* keys (product name, logo/favicon URLs, accent color, docs/support links). Public GET /branding so login and shell theme pre-auth; admin writes via PUT /settings. useBranding() applies document.title, accent CSS var and favicon; BrandWordmark replaces hardcoded wordmarks; admin Platform settings gain a Branding form. MCP containr_branding_get + settings_update. Generated with [Devin](https://devin.ai) Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
9408464f12
commit
b47ced0135
14 files changed
+408
-8
No files matched your search
@@ -0,0 +1,54 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getBranding, type Branding } from '@/lib/api-client';
|
||||
import { useDemoMode } from '@/lib/demo-mode';
|
||||
|
||||
const DEFAULT_TITLE = 'containr';
|
||||
|
||||
/**
|
||||
* Loads the instance's white-label branding (public endpoint) and applies
|
||||
* it: document title, favicon, and the accent CSS variable. Defaults keep
|
||||
* the app Containr-branded.
|
||||
*/
|
||||
export function useBranding(): Branding {
|
||||
const isDemoMode = useDemoMode();
|
||||
const brandingQuery = useQuery({
|
||||
queryKey: ['branding'],
|
||||
queryFn: getBranding,
|
||||
staleTime: 5 * 60_000,
|
||||
enabled: !isDemoMode,
|
||||
});
|
||||
|
||||
const branding = brandingQuery.data ?? {
|
||||
productName: 'Containr',
|
||||
logoUrl: '',
|
||||
faviconUrl: '',
|
||||
accentColor: '',
|
||||
docsUrl: '',
|
||||
supportUrl: '',
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const name = branding.productName;
|
||||
document.title = name && name !== 'Containr' ? name : DEFAULT_TITLE;
|
||||
|
||||
const root = document.documentElement;
|
||||
if (branding.accentColor && /^#[0-9a-fA-F]{3,8}$/.test(branding.accentColor)) {
|
||||
root.style.setProperty('--accent-primary', branding.accentColor);
|
||||
} else {
|
||||
root.style.removeProperty('--accent-primary');
|
||||
}
|
||||
|
||||
if (branding.faviconUrl) {
|
||||
let link = document.querySelector<HTMLLinkElement>('link[rel="icon"]');
|
||||
if (!link) {
|
||||
link = document.createElement('link');
|
||||
link.rel = 'icon';
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
link.href = branding.faviconUrl;
|
||||
}
|
||||
}, [branding.productName, branding.accentColor, branding.faviconUrl]);
|
||||
|
||||
return branding;
|
||||
}
|
||||
Reference in new issue
Block a user