From d6e3254a161b6b554c2eb789c6aedb44b6d33e59 Mon Sep 17 00:00:00 2001 From: Hampus Date: Thu, 8 Oct 2026 17:34:56 +0200 Subject: [PATCH] feat(app): show instance branding in switchers and pickers (#3289) --- .../UserAreaAccountSwitchMenu.module.css | 19 +++++ .../floating/UserAreaAccountSwitchMenu.tsx | 36 ++++++++- .../app/state/ActiveInstanceProductName.ts | 8 ++ .../features/auth/InstanceBranding.test.ts | 78 ++++++++++++++++++ .../src/features/auth/InstanceBranding.ts | 81 +++++++++++++++++++ .../features/auth/InstanceSnapshotLookup.ts | 19 +++++ .../auth/components/InstanceBrandMark.tsx | 60 ++++++++++++++ .../AccountInstanceBadge.tsx | 29 +++---- .../AccountProfilePicker.module.css | 10 ++- .../useAccountInstanceBadge.ts | 11 ++- .../state/useAuthorizeFlow.ts | 5 +- .../auth/flow/InstanceSelector.module.css | 8 ++ .../AuthLoginBrowserStep.module.css | 8 ++ .../auth_login_core/AuthLoginBrowserStep.tsx | 36 ++++----- .../instance_selector/InstancePickerRows.tsx | 19 +++-- .../channel/components/InviteEmbed.tsx | 6 +- .../components/barriers/BarrierComponents.tsx | 4 +- .../MembersTableHeaderRow.tsx | 4 +- .../features/devtools/utils/SelfXssNotice.ts | 7 +- .../components/ExpressionInfoCard.tsx | 6 +- .../src/features/i18n/locales/ar/messages.po | 30 +++---- .../src/features/i18n/locales/bg/messages.po | 30 +++---- .../src/features/i18n/locales/cs/messages.po | 30 +++---- .../src/features/i18n/locales/da/messages.po | 30 +++---- .../src/features/i18n/locales/de/messages.po | 30 +++---- .../src/features/i18n/locales/el/messages.po | 30 +++---- .../features/i18n/locales/en-GB/messages.po | 30 +++---- .../features/i18n/locales/en-US/messages.po | 30 +++---- .../features/i18n/locales/es-419/messages.po | 30 +++---- .../features/i18n/locales/es-ES/messages.po | 30 +++---- .../src/features/i18n/locales/fi/messages.po | 30 +++---- .../src/features/i18n/locales/fr/messages.po | 30 +++---- .../src/features/i18n/locales/he/messages.po | 30 +++---- .../src/features/i18n/locales/hi/messages.po | 30 +++---- .../src/features/i18n/locales/hr/messages.po | 30 +++---- .../src/features/i18n/locales/hu/messages.po | 30 +++---- .../src/features/i18n/locales/id/messages.po | 30 +++---- .../src/features/i18n/locales/it/messages.po | 30 +++---- .../src/features/i18n/locales/ja/messages.po | 30 +++---- .../src/features/i18n/locales/ko/messages.po | 30 +++---- .../src/features/i18n/locales/lt/messages.po | 30 +++---- .../src/features/i18n/locales/nl/messages.po | 30 +++---- .../src/features/i18n/locales/no/messages.po | 30 +++---- .../src/features/i18n/locales/pl/messages.po | 30 +++---- .../features/i18n/locales/pt-BR/messages.po | 30 +++---- .../src/features/i18n/locales/ro/messages.po | 30 +++---- .../src/features/i18n/locales/ru/messages.po | 30 +++---- .../features/i18n/locales/sv-SE/messages.po | 30 +++---- .../src/features/i18n/locales/th/messages.po | 30 +++---- .../src/features/i18n/locales/tr/messages.po | 30 +++---- .../src/features/i18n/locales/uk/messages.po | 30 +++---- .../src/features/i18n/locales/vi/messages.po | 30 +++---- .../features/i18n/locales/zh-CN/messages.po | 30 +++---- .../features/i18n/locales/zh-TW/messages.po | 30 +++---- .../components/DisableInvitesButton.tsx | 4 +- .../alerts/InvitesDisabledModal.tsx | 6 +- .../components/modals/InviteAcceptModal.tsx | 4 +- .../components/pages/InviteLoginPage.tsx | 4 +- .../components/pages/InviteRegisterPage.tsx | 4 +- .../messaging/commands/MessageCommands.tsx | 4 +- .../modals/ExternalLinkWarningModal.tsx | 4 +- .../components/report/ReportStepComplete.tsx | 4 +- .../components/report_modal/IARModalCopy.ts | 8 +- 63 files changed, 929 insertions(+), 587 deletions(-) create mode 100644 fluxer_app/src/features/app/state/ActiveInstanceProductName.ts create mode 100644 fluxer_app/src/features/auth/InstanceBranding.test.ts create mode 100644 fluxer_app/src/features/auth/InstanceBranding.ts create mode 100644 fluxer_app/src/features/auth/InstanceSnapshotLookup.ts create mode 100644 fluxer_app/src/features/auth/components/InstanceBrandMark.tsx diff --git a/fluxer_app/src/features/app/components/floating/UserAreaAccountSwitchMenu.module.css b/fluxer_app/src/features/app/components/floating/UserAreaAccountSwitchMenu.module.css index 6962c8a32..4cc604d7c 100644 --- a/fluxer_app/src/features/app/components/floating/UserAreaAccountSwitchMenu.module.css +++ b/fluxer_app/src/features/app/components/floating/UserAreaAccountSwitchMenu.module.css @@ -31,6 +31,25 @@ line-height: 0.875rem; } +.instanceHeadingMark { + display: inline-flex; + flex: 0 0 auto; + width: 0.75rem; + height: 0.75rem; + align-items: center; + justify-content: center; + overflow: hidden; + border-radius: 0.1875rem; +} + +.instanceHeadingMark svg, +.instanceHeadingMark img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} + .instanceHeadingLabel { min-width: 0; overflow: hidden; diff --git a/fluxer_app/src/features/app/components/floating/UserAreaAccountSwitchMenu.tsx b/fluxer_app/src/features/app/components/floating/UserAreaAccountSwitchMenu.tsx index 84cdea45c..748b8137e 100644 --- a/fluxer_app/src/features/app/components/floating/UserAreaAccountSwitchMenu.tsx +++ b/fluxer_app/src/features/app/components/floating/UserAreaAccountSwitchMenu.tsx @@ -9,6 +9,9 @@ import { } from '@app/features/auth/components/accounts/AccountIdentity'; import {getAccountMentionCount} from '@app/features/auth/components/accounts/account_switcher_modal/AccountMentionCounts'; import {SESSION_EXPIRED_DESCRIPTOR} from '@app/features/auth/components/accounts/account_switcher_modal/AccountPickerShared'; +import {InstanceBrandMark} from '@app/features/auth/components/InstanceBrandMark'; +import {resolveInstanceLabel} from '@app/features/auth/flow/instance_selector/InstanceDirectoryStorage'; +import {resolveInstanceBrandIconUrl, resolveInstanceProductName} from '@app/features/auth/InstanceBranding'; import {getAccountKey} from '@app/features/auth/state/AccountStorageKey'; import {ACTIVE_ACCOUNT_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors'; import type {Account} from '@app/features/platform/state/AuthSession'; @@ -18,6 +21,7 @@ import {MentionBadge} from '@app/features/ui/components/MentionBadge'; import {MockAvatar} from '@app/features/ui/components/MockAvatar'; import FocusRing from '@app/features/ui/focus_ring/FocusRing'; import {flxElementClassName} from '@app/lib/react'; +import {isOfficialInstanceHost} from '@fluxer/instance_bootstrap/src/OfficialInstance'; import {Trans, useLingui} from '@lingui/react/macro'; import {CheckIcon, GearIcon} from '@phosphor-icons/react'; import clsx from 'clsx'; @@ -30,6 +34,8 @@ const UNKNOWN_INSTANCE_GROUP_KEY = 'unknown'; interface AccountInstanceGroup { readonly groupKey: string; readonly instanceLabel: string | null; + readonly isOfficial: boolean; + readonly iconUrl: string | null; readonly accounts: Array; } @@ -46,7 +52,13 @@ function createAccountInstanceGroups(accounts: ReadonlyArray): Array): Array + + + , origin = 'https://chat.example.org') { + return { + apiEndpoint: `${origin}/api`, + apiPublicEndpoint: `${origin}/api`, + mediaEndpoint: `${origin}/media`, + staticCdnEndpoint: origin, + webAppEndpoint: origin, + appPublic: { + branding: { + product_name: 'Example Chat', + icon_url: null, + symbol_url: null, + logo_url: null, + wordmark_url: null, + favicon_url: null, + theme_color: null, + status_page_url: null, + status_page_incident_history_url: null, + premium_product_name: 'Premium', + premium_info_url: null, + ...branding, + }, + setup: {configured: true, admin_url: null}, + legal: {terms_url: null, privacy_url: null}, + registration: {collect_date_of_birth: false}, + }, + }; +} + +describe('instance branding', () => { + test('prefers the symbol, then the icon, then the logo', () => { + const base = 'https://chat.example.org/media/branding'; + expect( + resolveInstanceBrandIconUrl( + snapshot({symbol_url: `${base}/s.png`, icon_url: `${base}/i.png`, logo_url: `${base}/l.png`}), + ), + ).toBe(`${base}/s.png`); + expect(resolveInstanceBrandIconUrl(snapshot({icon_url: `${base}/i.png`, logo_url: `${base}/l.png`}))).toBe( + `${base}/i.png`, + ); + expect(resolveInstanceBrandIconUrl(snapshot({logo_url: `${base}/l.png`}))).toBe(`${base}/l.png`); + expect(resolveInstanceBrandIconUrl(snapshot({}))).toBeNull(); + }); + + test('only accepts assets served by the instance itself', () => { + const s = snapshot({}); + expect(resolveInstanceAssetUrl(s, '/media/branding/icon.png')).toBe( + 'https://chat.example.org/media/branding/icon.png', + ); + expect(resolveInstanceAssetUrl(s, 'https://tracker.example.net/icon.png')).toBeNull(); + expect(resolveInstanceAssetUrl(s, 'javascript:alert(1)')).toBeNull(); + expect(resolveInstanceAssetUrl(s, 'https://user:pass@chat.example.org/icon.png')).toBeNull(); + expect(resolveInstanceAssetUrl(s, 'http://chat.example.org/icon.png')).toBeNull(); + }); + + test('a cleartext instance may serve its own assets over http', () => { + const s = snapshot({icon_url: 'http://192.168.1.20:8080/media/icon.png'}, 'http://192.168.1.20:8080'); + expect(resolveInstanceBrandIconUrl(s)).toBe('http://192.168.1.20:8080/media/icon.png'); + }); + + test('product name ignores blank values', () => { + expect(resolveInstanceProductName(snapshot({product_name: ' Example Chat '}))).toBe('Example Chat'); + expect(resolveInstanceProductName(snapshot({product_name: ' '}))).toBeNull(); + expect(resolveInstanceProductName(null)).toBeNull(); + }); +}); diff --git a/fluxer_app/src/features/auth/InstanceBranding.ts b/fluxer_app/src/features/auth/InstanceBranding.ts new file mode 100644 index 000000000..4ec0b8362 --- /dev/null +++ b/fluxer_app/src/features/auth/InstanceBranding.ts @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import type {RuntimeConfigSnapshot} from '@app/features/app/state/RuntimeConfigSnapshot'; + +type BrandingSnapshot = Pick< + RuntimeConfigSnapshot, + 'apiEndpoint' | 'apiPublicEndpoint' | 'mediaEndpoint' | 'staticCdnEndpoint' | 'webAppEndpoint' | 'appPublic' +>; + +const ASSET_PROTOCOLS: ReadonlySet = new Set(['https:', 'http:']); + +function originOf(value: string | null | undefined): string | null { + if (value == null || value.length === 0) { + return null; + } + try { + return new URL(value).origin; + } catch { + return null; + } +} + +function instanceAssetOrigins(snapshot: BrandingSnapshot): ReadonlySet { + const origins = new Set(); + for (const endpoint of [ + snapshot.mediaEndpoint, + snapshot.staticCdnEndpoint, + snapshot.webAppEndpoint, + snapshot.apiEndpoint, + snapshot.apiPublicEndpoint, + ]) { + const origin = originOf(endpoint); + if (origin != null && origin !== 'null') { + origins.add(origin); + } + } + return origins; +} + +export function resolveInstanceAssetUrl( + snapshot: BrandingSnapshot, + candidate: string | null | undefined, +): string | null { + const trimmed = candidate?.trim(); + if (trimmed == null || trimmed.length === 0) { + return null; + } + const base = snapshot.mediaEndpoint || snapshot.webAppEndpoint; + let url: URL; + try { + url = new URL(trimmed, base); + } catch { + return null; + } + if (!ASSET_PROTOCOLS.has(url.protocol) || url.username !== '' || url.password !== '') { + return null; + } + if (!instanceAssetOrigins(snapshot).has(url.origin)) { + return null; + } + return url.href; +} + +export function resolveInstanceBrandIconUrl(snapshot: BrandingSnapshot | null | undefined): string | null { + const branding = snapshot?.appPublic?.branding; + if (snapshot == null || branding == null) { + return null; + } + for (const candidate of [branding.symbol_url, branding.icon_url, branding.logo_url]) { + const url = resolveInstanceAssetUrl(snapshot, candidate); + if (url != null) { + return url; + } + } + return null; +} + +export function resolveInstanceProductName(snapshot: BrandingSnapshot | null | undefined): string | null { + const name = snapshot?.appPublic?.branding?.product_name?.trim(); + return name == null || name.length === 0 ? null : name; +} diff --git a/fluxer_app/src/features/auth/InstanceSnapshotLookup.ts b/fluxer_app/src/features/auth/InstanceSnapshotLookup.ts new file mode 100644 index 000000000..0840e096d --- /dev/null +++ b/fluxer_app/src/features/auth/InstanceSnapshotLookup.ts @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import InstanceSnapshotStore from '@app/features/app/state/InstanceSnapshotStore'; +import type {RuntimeConfigSnapshot} from '@app/features/app/state/RuntimeConfigSnapshot'; +import {resolveAccountInstanceKey} from '@app/features/auth/AccountDisplayUtils'; +import Accounts from '@app/features/auth/state/Accounts'; + +export function findInstanceSnapshot(instanceKey: string): RuntimeConfigSnapshot | null { + const cached = InstanceSnapshotStore.get(instanceKey); + if (cached != null) { + return cached; + } + for (const account of Accounts.orderedAccounts) { + if (account.instance != null && resolveAccountInstanceKey(account) === instanceKey) { + return account.instance; + } + } + return null; +} diff --git a/fluxer_app/src/features/auth/components/InstanceBrandMark.tsx b/fluxer_app/src/features/auth/components/InstanceBrandMark.tsx new file mode 100644 index 000000000..59b0e0797 --- /dev/null +++ b/fluxer_app/src/features/auth/components/InstanceBrandMark.tsx @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {remFromPx} from '@app/features/theme/layout/RemFromPx'; +import FluxerLogoAsset from '@app/media/images/fluxer-logo-color.svg?react'; +import {GlobeIcon} from '@phosphor-icons/react'; +import type React from 'react'; +import {useState} from 'react'; + +interface InstanceBrandMarkProps { + readonly isOfficial: boolean; + readonly iconUrl: string | null; + readonly size: number; + readonly globeWeight?: 'regular' | 'bold'; + readonly 'data-flx'?: string; +} + +function InstanceBrandImage({ + url, + size, + fallback, +}: { + readonly url: string; + readonly size: number; + readonly fallback: React.ReactElement; +}): React.ReactElement { + const [failed, setFailed] = useState(false); + if (failed) { + return fallback; + } + return ( + setFailed(true)} + data-flx="auth.components.instance-brand-mark.instance-brand-image.image" + /> + ); +} + +export function InstanceBrandMark({ + isOfficial, + iconUrl, + size, + globeWeight = 'regular', + 'data-flx': dataFlx, +}: InstanceBrandMarkProps): React.ReactElement { + if (isOfficial) { + return {isOfficial && ( - {isOfficial ? ( - - ) : ( - - )} + ); diff --git a/fluxer_app/src/features/auth/components/accounts/account_switcher_modal/AccountProfilePicker.module.css b/fluxer_app/src/features/auth/components/accounts/account_switcher_modal/AccountProfilePicker.module.css index 44b33ce57..e1cb5d175 100644 --- a/fluxer_app/src/features/auth/components/accounts/account_switcher_modal/AccountProfilePicker.module.css +++ b/fluxer_app/src/features/auth/components/accounts/account_switcher_modal/AccountProfilePicker.module.css @@ -281,14 +281,20 @@ box-shadow: 0 0.25rem 0.75rem rgb(0 0 0 / 0.28); } -.instanceBadge svg, -.instanceBadge img { +.instanceBadge svg { display: block; width: 72%; height: 72%; object-fit: cover; } +.instanceBadge img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} + .instanceTooltip { display: inline-flex; align-items: center; diff --git a/fluxer_app/src/features/auth/components/accounts/account_switcher_modal/useAccountInstanceBadge.ts b/fluxer_app/src/features/auth/components/accounts/account_switcher_modal/useAccountInstanceBadge.ts index 5590feab1..8e9117507 100644 --- a/fluxer_app/src/features/auth/components/accounts/account_switcher_modal/useAccountInstanceBadge.ts +++ b/fluxer_app/src/features/auth/components/accounts/account_switcher_modal/useAccountInstanceBadge.ts @@ -6,6 +6,7 @@ import { normalizeInstanceName, resolveInstanceLabel, } from '@app/features/auth/flow/instance_selector/InstanceDirectoryStorage'; +import {resolveInstanceBrandIconUrl} from '@app/features/auth/InstanceBranding'; import type {Account} from '@app/features/platform/state/AuthSession'; import {isOfficialInstanceHost, OFFICIAL_INSTANCE_NAME} from '@fluxer/instance_bootstrap/src/OfficialInstance'; import {useMemo} from 'react'; @@ -14,6 +15,7 @@ export interface AccountInstanceBadgeInfo { readonly label: string; readonly title: string; readonly isOfficial: boolean; + readonly iconUrl: string | null; } function findKnownInstance(instanceKey: string, knownInstances: ReadonlyArray): InstanceInfo | null { @@ -44,10 +46,15 @@ function getAccountInstanceBadgeInfo( } const isOfficial = isOfficialInstanceHost(instanceKey); if (isOfficial) { - return {label: OFFICIAL_INSTANCE_NAME, title: domain, isOfficial}; + return {label: OFFICIAL_INSTANCE_NAME, title: domain, isOfficial, iconUrl: null}; } const knownInstance = findKnownInstance(instanceKey, knownInstances); - return {label: resolveInstanceName(account, knownInstance, domain), title: domain, isOfficial}; + return { + label: resolveInstanceName(account, knownInstance, domain), + title: domain, + isOfficial, + iconUrl: resolveInstanceBrandIconUrl(account.instance), + }; } export function useAccountInstanceBadge( diff --git a/fluxer_app/src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts b/fluxer_app/src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts index 8bb6719cb..da34d6995 100644 --- a/fluxer_app/src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts +++ b/fluxer_app/src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants'; import {Endpoints} from '@app/features/app/constants/Endpoints'; +import {getActiveInstanceProductName} from '@app/features/app/state/ActiveInstanceProductName'; import {useAuthorizeParams} from '@app/features/auth/components/pages/oauth_authorize_page/hooks/useAuthorizeParams'; import { type BotInviteDestinationOption, @@ -459,7 +459,8 @@ export function useAuthorizeFlow(options: UseAuthorizeFlowOptions = {}): Authori } }, [params, onCancelOverride, publicAppState.data, i18n]); const sessionExpiredMessage = useMemo( - () => i18n._(YOUR_PRODUCT_SESSION_EXPIRED_SIGN_IN_AGAIN_TO_DESCRIPTOR, {productName: PRODUCT_NAME}), + () => + i18n._(YOUR_PRODUCT_SESSION_EXPIRED_SIGN_IN_AGAIN_TO_DESCRIPTOR, {productName: getActiveInstanceProductName()}), [i18n.locale], ); return { diff --git a/fluxer_app/src/features/auth/flow/InstanceSelector.module.css b/fluxer_app/src/features/auth/flow/InstanceSelector.module.css index 949ab1f54..f17841c99 100644 --- a/fluxer_app/src/features/auth/flow/InstanceSelector.module.css +++ b/fluxer_app/src/features/auth/flow/InstanceSelector.module.css @@ -162,6 +162,14 @@ object-fit: contain; } +.instanceLogo img { + display: block; + width: 1.5rem; + height: 1.5rem; + border-radius: 0.25rem; + object-fit: cover; +} + .instanceMeta { display: grid; gap: 0.125rem; diff --git a/fluxer_app/src/features/auth/flow/auth_login_core/AuthLoginBrowserStep.module.css b/fluxer_app/src/features/auth/flow/auth_login_core/AuthLoginBrowserStep.module.css index e027fde82..ed0056cbb 100644 --- a/fluxer_app/src/features/auth/flow/auth_login_core/AuthLoginBrowserStep.module.css +++ b/fluxer_app/src/features/auth/flow/auth_login_core/AuthLoginBrowserStep.module.css @@ -45,6 +45,14 @@ object-fit: contain; } +.browserInstanceLogo img { + display: block; + width: 1.5rem; + height: 1.5rem; + border-radius: 0.25rem; + object-fit: cover; +} + .browserInstanceMeta { display: grid; gap: 0.125rem; diff --git a/fluxer_app/src/features/auth/flow/auth_login_core/AuthLoginBrowserStep.tsx b/fluxer_app/src/features/auth/flow/auth_login_core/AuthLoginBrowserStep.tsx index aab40cda4..c08643fd2 100644 --- a/fluxer_app/src/features/auth/flow/auth_login_core/AuthLoginBrowserStep.tsx +++ b/fluxer_app/src/features/auth/flow/auth_login_core/AuthLoginBrowserStep.tsx @@ -2,6 +2,7 @@ import type {RuntimeConfigSnapshot} from '@app/features/app/state/InstanceSnapshotStore'; import {resolveSnapshotInstanceDomain} from '@app/features/auth/AccountDisplayUtils'; +import {InstanceBrandMark} from '@app/features/auth/components/InstanceBrandMark'; import loginStyles from '@app/features/auth/components/pages/LoginPage.module.css'; import styles from '@app/features/auth/flow/auth_login_core/AuthLoginBrowserStep.module.css'; import { @@ -15,6 +16,7 @@ import { resolveInstanceLabel, } from '@app/features/auth/flow/instance_selector/InstanceDirectoryStorage'; import {useKnownInstances} from '@app/features/auth/flow/instance_selector/useKnownInstances'; +import {resolveInstanceBrandIconUrl} from '@app/features/auth/InstanceBranding'; import type {LoginSuccessPayload} from '@app/features/auth/state/AuthFlow'; import { BACK_DESCRIPTOR, @@ -33,7 +35,6 @@ import FocusRing from '@app/features/ui/focus_ring/FocusRing'; import {SteppedCarousel, SteppedCarouselActions} from '@app/features/ui/stepped_carousel/SteppedCarousel'; import {isDesktop} from '@app/features/ui/utils/NativeUtils'; import {flxElementClassName} from '@app/lib/react'; -import FluxerLogoAsset from '@app/media/images/fluxer-logo-color.svg?react'; import {DesktopHandoffReturnMethod} from '@fluxer/desktop_ipc/src/BrowserHandoffContract'; import {isOfficialInstanceHost, OFFICIAL_INSTANCE_NAME} from '@fluxer/instance_bootstrap/src/OfficialInstance'; import {msg} from '@lingui/core/macro'; @@ -44,7 +45,6 @@ import { ArrowSquareOutIcon, CheckCircleIcon, ClipboardIcon, - GlobeIcon, WarningCircleIcon, } from '@phosphor-icons/react'; import {observer} from 'mobx-react-lite'; @@ -94,6 +94,7 @@ interface BrowserInstanceIdentity { readonly domain: string; readonly isOfficial: boolean; readonly name: string; + readonly iconUrl: string | null; } function findKnownInstanceName(domain: string, knownInstances: ReadonlyArray): string | null { @@ -118,13 +119,19 @@ function resolveBrowserInstanceIdentity({ const domain = resolveSnapshotInstanceDomain(snapshot) ?? apiEndpoint; const isOfficial = isOfficialInstanceHost(apiEndpoint); if (isOfficial) { - return {domain, isOfficial, name: OFFICIAL_INSTANCE_NAME}; + return {domain, isOfficial, name: OFFICIAL_INSTANCE_NAME, iconUrl: null}; } + const iconUrl = resolveInstanceBrandIconUrl(snapshot); const productLabel = resolveInstanceLabel(snapshot.appPublic?.branding?.product_name, domain); if (productLabel !== instanceDomainHost(domain)) { - return {domain, isOfficial, name: productLabel}; + return {domain, isOfficial, name: productLabel, iconUrl}; } - return {domain, isOfficial, name: resolveInstanceLabel(findKnownInstanceName(domain, knownInstances), domain)}; + return { + domain, + isOfficial, + name: resolveInstanceLabel(findKnownInstanceName(domain, knownInstances), domain), + iconUrl, + }; } function resolveSignInAddress(webAppEndpoint: string): string { @@ -558,19 +565,12 @@ export const AuthLoginBrowserStep = observer(function AuthLoginBrowserStep({ className={styles.browserInstanceLogo} data-flx="auth.flow.auth-login-core.auth-login-browser-step.browser-instance-logo" > - {instanceIdentity.isOfficial ? ( - - ) : ( - - )} + { - const name = resolveInstanceLabel(instance.name, instance.domain); + const snapshot = findInstanceSnapshot(instance.instanceKey); + const storedName = instance.name !== instance.domain ? instance.name : null; + const name = resolveInstanceLabel(storedName ?? resolveInstanceProductName(snapshot), instance.domain); + const iconUrl = resolveInstanceBrandIconUrl(snapshot); return (
- {guildActionState.isRaidDetected - ? i18n._(DETECTED_A_POTENTIAL_RAID_SO_NEW_USERS_CAN_DESCRIPTOR, {productName: PRODUCT_NAME}) + ? i18n._(DETECTED_A_POTENTIAL_RAID_SO_NEW_USERS_CAN_DESCRIPTOR, { + productName: getActiveInstanceProductName(), + }) : i18n._(INVITES_ARE_CURRENTLY_PAUSED_FOR_THIS_COMMUNITY_DESCRIPTOR)}

)} diff --git a/fluxer_app/src/features/channel/components/barriers/BarrierComponents.tsx b/fluxer_app/src/features/channel/components/barriers/BarrierComponents.tsx index 9ee395129..88d601108 100644 --- a/fluxer_app/src/features/channel/components/barriers/BarrierComponents.tsx +++ b/fluxer_app/src/features/channel/components/barriers/BarrierComponents.tsx @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import {UserSettingsModal} from '@app/features/app/components/dialogs/LoadableSettingsModals'; -import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants'; +import {getActiveInstanceProductName} from '@app/features/app/state/ActiveInstanceProductName'; import {openClaimAccountModal} from '@app/features/auth/components/modals/ClaimAccountModal'; import styles from '@app/features/channel/components/barriers/BarrierComponents.module.css'; import {openChannelFollowModal} from '@app/features/channel/components/modals/ChannelFollowModal'; @@ -359,7 +359,7 @@ export const SystemDmBarrier = observer(() => { const {i18n} = useLingui(); return (
- {i18n._(JOINED_PRODUCT_DESCRIPTOR, {productName: PRODUCT_NAME})} + {i18n._(JOINED_PRODUCT_DESCRIPTOR, {productName: getActiveInstanceProductName()})}

- {i18n._(LEAVING_PRODUCT_DESCRIPTOR, {productName: PRODUCT_NAME})} + {i18n._(LEAVING_PRODUCT_DESCRIPTOR, {productName: getActiveInstanceProductName()})}

External links can be dangerous. Be careful. diff --git a/fluxer_app/src/features/moderation/components/report/ReportStepComplete.tsx b/fluxer_app/src/features/moderation/components/report/ReportStepComplete.tsx index 2ecccd78b..bc9e9ded0 100644 --- a/fluxer_app/src/features/moderation/components/report/ReportStepComplete.tsx +++ b/fluxer_app/src/features/moderation/components/report/ReportStepComplete.tsx @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import {StatusSlate} from '@app/features/app/components/dialogs/shared/StatusSlate'; -import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants'; +import {getActiveInstanceProductName} from '@app/features/app/state/ActiveInstanceProductName'; import {msg} from '@lingui/core/macro'; import {Trans, useLingui} from '@lingui/react/macro'; import {CheckCircleIcon} from '@phosphor-icons/react'; @@ -29,7 +29,7 @@ export const ReportStepComplete: React.FC = ({onStartOver}) => { Report sent} - description={i18n._(REPORT_COMPLETE_DESCRIPTION_DESCRIPTOR, {productName: PRODUCT_NAME})} + description={i18n._(REPORT_COMPLETE_DESCRIPTION_DESCRIPTOR, {productName: getActiveInstanceProductName()})} iconStyle={{color: 'var(--status-online)'}} actions={[ { diff --git a/fluxer_app/src/features/moderation/components/report_modal/IARModalCopy.ts b/fluxer_app/src/features/moderation/components/report_modal/IARModalCopy.ts index edab6796f..f14dd6ae9 100644 --- a/fluxer_app/src/features/moderation/components/report_modal/IARModalCopy.ts +++ b/fluxer_app/src/features/moderation/components/report_modal/IARModalCopy.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants'; +import {getActiveInstanceProductName} from '@app/features/app/state/ActiveInstanceProductName'; import RuntimeConfig from '@app/features/app/state/RuntimeConfig'; import {CLOSE_DM_DESCRIPTOR} from '@app/features/channel/utils/ChannelMessageDescriptors'; import { @@ -580,7 +580,7 @@ function getMessageRuleReasonOptions(i18n: I18n): Array