fix(selfhost): branding image origins and theme colour (#3389)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-11 13:31:05 +02:00
1 parent cca8227ca6
commit edc913096a
27 files changed
+724 -49

No files matched your search

@@ -161,10 +161,10 @@ const STEP_NAVIGATION_LOCK_MS = 320;
const BRANDING_ASSET_KINDS: ReadonlyArray<SetupBrandingAssetKind> = ['icon', 'symbol', 'logo', 'wordmark', 'favicon'];
function hexToNumber(value: string | null): number {
if (!value) return 0;
function hexToNumber(value: string | null): number | null {
if (!value) return null;
const match = /^#?([0-9a-fA-F]{6})$/.exec(value.trim());
if (!match) return 0;
if (!match) return null;
return parseInt(match[1], 16) >>> 0;
}
@@ -463,7 +463,7 @@ export const SelfHostedSetupWizardGate = observer(() => {
const [creatingRecoveryKit, setCreatingRecoveryKit] = useState(false);
const [productName, setProductName] = useState('');
const [themeColor, setThemeColor] = useState(0);
const [themeColor, setThemeColor] = useState<number | null>(null);
const [registrationMode, setRegistrationMode] = useState<RegistrationMode>('open');
const [singleCommunityEnabled, setSingleCommunityEnabled] = useState(false);
const [singleCommunityName, setSingleCommunityName] = useState('');
@@ -902,7 +902,7 @@ export const SelfHostedSetupWizardGate = observer(() => {
app_public: {
branding: {
product_name: productNameTrimmed,
theme_color: numberToHexColor(themeColor),
theme_color: themeColor === null ? null : numberToHexColor(themeColor),
},
setup: {configured: true},
},
@@ -34,6 +34,7 @@ import type React from 'react';
import {useCallback} from 'react';
const PUSH_RELAY_NOTICE_URL = 'https://fluxer.com/push-relay';
const DEFAULT_THEME_COLOR = 0x4641d9;
export type RegistrationMode = 'open' | 'approval' | 'closed';
export type PremiumMode = 'mirror' | 'everyone';
@@ -844,11 +845,11 @@ export const BrandingStep = observer(
}: {
productName: string;
productNameError: boolean;
themeColor: number;
themeColor: number | null;
assets: ReadonlyArray<BrandingAssetState>;
disabled: boolean;
onProductNameChange: (value: string) => void;
onThemeColorChange: (value: number) => void;
onThemeColorChange: (value: number | null) => void;
onUploadAsset: (kind: SetupBrandingAssetKind) => void;
onClearAsset: (kind: SetupBrandingAssetKind) => void;
}) => {
@@ -873,8 +874,11 @@ export const BrandingStep = observer(
/>
<ColorPickerField
label={i18n._(THEME_COLOR_LABEL_DESCRIPTOR)}
value={themeColor}
value={themeColor ?? DEFAULT_THEME_COLOR}
onChange={onThemeColorChange}
defaultValue={DEFAULT_THEME_COLOR}
isDefaultValue={themeColor === null}
onReset={() => onThemeColorChange(null)}
disabled={disabled}
data-flx="app.self-hosted-setup-wizard-gate.theme-color-field"
/>
@@ -11,6 +11,10 @@ export const NATIVE_NOTIFICATION_ICON_CSS_SIZE: MediaProxyImageSize = 128;
export function getNotificationIconURL(user: Pick<User, 'id' | 'avatar'>, guildId?: string | null): string {
const member = guildId ? GuildMembers.getMember(guildId, user.id) : null;
const nativeIcon = isDesktop();
const systemAvatarUrl = AvatarUtils.getSystemUserBrandingAvatarURL(user.id);
if (systemAvatarUrl !== null) {
return systemAvatarUrl;
}
if (guildId && member) {
return nativeIcon
? AvatarUtils.getGuildMemberNotificationAvatarURL({
@@ -2,7 +2,7 @@
import * as ColorUtils from '@app/features/theme/utils/ColorUtils';
import type {User} from '@app/features/user/models/User';
import {getDefaultAvatarPrimaryColor} from '@app/features/user/utils/AvatarUtils';
import {getDefaultAvatarPrimaryColor, getSystemUserBrandingColor} from '@app/features/user/utils/AvatarUtils';
import {DEFAULT_ACCENT_COLOR} from '@fluxer/constants/src/AppConstants';
type RawAccentColor = number | null | undefined;
@@ -19,6 +19,10 @@ export function getUserAccentColor(
profileAccentColor?: RawAccentColor,
fallback = DEFAULT_ACCENT_COLOR,
): string {
const systemColor = user ? getSystemUserBrandingColor(user.id) : null;
if (systemColor) {
return systemColor;
}
const profileColor = getAccentColorHex(profileAccentColor);
if (profileColor) {
return profileColor;
@@ -1,11 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
import {getUserAccentColor} from '@app/features/theme/utils/AccentColorUtils';
import {ProfileBody} from '@app/features/user/components/modals/user_profile_modal/ProfileBody';
import {ProfileMediaHeader} from '@app/features/user/components/modals/user_profile_modal/ProfileMediaHeader';
import type {ProfileModalContentProps} from '@app/features/user/components/modals/user_profile_modal/UserProfileModalShared';
import * as AvatarUtils from '@app/features/user/utils/AvatarUtils';
import * as ProfileDisplayUtils from '@app/features/user/utils/ProfileDisplayUtils';
import {resolveProfileGuildMembership, toProfileDisplayContext} from '@app/features/user/utils/ProfileGuildMembership';
import {FLUXERBOT_ID} from '@fluxer/constants/src/AppConstants';
@@ -30,11 +28,7 @@ export const ProfileModalContent: React.FC<ProfileModalContentProps> = observer(
}) => {
const effectiveProfile = profile?.getEffectiveProfile() ?? null;
const isSystemUser = user.id === FLUXERBOT_ID;
const systemThemeColor =
isSystemUser && RuntimeConfig.isSelfHosted()
? RuntimeConfig.getSnapshotOrNull()?.appPublic.branding.theme_color
: null;
const bannerColor = systemThemeColor ?? getUserAccentColor(user, effectiveProfile?.accent_color);
const bannerColor = getUserAccentColor(user, effectiveProfile?.accent_color);
const membership = resolveProfileGuildMembership(profile);
const profileContext = useMemo<ProfileDisplayUtils.ProfileDisplayContext>(
() =>
@@ -46,13 +40,10 @@ export const ProfileModalContent: React.FC<ProfileModalContentProps> = observer(
}),
[user, profile, membership],
);
const {avatarUrl: profileAvatarUrl, hoverAvatarUrl: profileHoverAvatarUrl} = useMemo(
const {avatarUrl, hoverAvatarUrl} = useMemo(
() => ProfileDisplayUtils.getProfileAvatarUrls(profileContext, previewOverrides, MEDIA_PROXY_AVATAR_SIZE_PROFILE),
[profileContext, previewOverrides],
);
const systemAvatarUrl = AvatarUtils.getSystemUserBrandingAvatarURL(user.id);
const avatarUrl = systemAvatarUrl ?? profileAvatarUrl;
const hoverAvatarUrl = systemAvatarUrl ?? profileHoverAvatarUrl;
const {bannerUrl, hoverBannerUrl} = useMemo(
() =>
ProfileDisplayUtils.getProfileBannerUrls(
@@ -82,6 +82,11 @@ export function getSystemUserBrandingAvatarURL(userId: string): string | null {
return branding?.icon_url ?? branding?.logo_url ?? null;
}
export function getSystemUserBrandingColor(userId: string): string | null {
if (userId !== FLUXERBOT_ID || !RuntimeConfig.isSelfHosted()) return null;
return RuntimeConfig.getSnapshotOrNull()?.appPublic.branding.theme_color ?? null;
}
export function getDefaultAvatarPrimaryColor(id: string) {
return getSharedDefaultAvatarPrimaryColor(id);
}
@@ -43,6 +43,10 @@ function getProfileAvatarUrl(
if (previewAvatarUrl) {
return previewAvatarUrl;
}
const systemAvatarUrl = AvatarUtils.getSystemUserBrandingAvatarURL(user.id);
if (systemAvatarUrl !== null) {
return systemAvatarUrl;
}
if (!ignoreGuildAvatar && guildId && guildMember) {
return AvatarUtils.getGuildMemberDisplayAvatarURL({
guildId,