Files
e66195e5bb fix(branding): render tenant logos unoptimized so self-hosted sidebars work (#2203) (#2246)
* fix(branding): render tenant logos unoptimized so self-hosted sidebars work (#2203)

On the official Docker image a byrå logo uploaded under Settings > Brand
worked as favicon but rendered broken in the sidebar. BrandHomeLink (and
BrandWordmark) sent the Supabase Storage URL through the Next.js image
optimizer, whose remote-host allowlist is derived from
NEXT_PUBLIC_SUPABASE_URL at BUILD time. The generic image bakes a sentinel
there and docker-entrypoint.sh substitutes the real URL only at container
start, so /_next/image answered 400 '"url" parameter is not allowed'.

Both tenant-logo <Image> elements now pass `unoptimized`: the browser
fetches the public object directly, which is exactly what the favicon
already did, and CSP img-src already permits https:. The remotePatterns
block in next.config.ts stays for builds that know the URL, with its
comment updated to say what it still covers.

Closes #2203

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VnConrmMCxJRQ5kfiPPWyy

* chore: carry the DECISIONS.md line for this PR in #2247 instead (append-only log conflicts on every merge)

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 18:41:40 +02:00

74 lines
2.4 KiB
TypeScript

'use client'
import Image from 'next/image'
import { cn } from '@/lib/utils'
import { useBranding } from '@/lib/branding/brand-context'
interface BrandWordmarkProps {
/**
* Visual size. `'hero'` is for landing/auth/onboarding hero slots (~the
* same vertical weight as the old 240px logo image). `'inline'` matches
* the old 30px image used in top-left nav contexts.
*/
size?: 'hero' | 'inline'
/**
* Force lowercase rendering. Defaults to true to match the existing
* font-display + `.toLowerCase()` pattern used elsewhere in the app.
*/
lowercase?: boolean
className?: string
}
/**
* Wordmark used in place of the legacy logo image on auth / onboarding /
* sandbox / invite surfaces. On a branded host with an uploaded logo it
* renders the logo image ALONE (founder call 2026-08-05: byrå logos usually
* carry their own name, so logo + text read as a duplicate); otherwise it
* renders exactly the text-only wordmark: the active brand's `appName` in
* Hedvig Letters Serif at weight 700 (the display font is single-weight on
* Google Fonts so 700 ends up synthetically bolded, but that matches the
* requested aesthetic).
*/
export function BrandWordmark({
size = 'hero',
lowercase = true,
className,
}: BrandWordmarkProps) {
const branding = useBranding()
const name = lowercase ? branding.appName.toLowerCase() : branding.appName
if (branding.logoUrl) {
// The logo carries the brand alone; the app name moves into alt text so
// the image keeps an accessible name.
return (
<span className={cn('inline-flex items-center', className)}>
<Image
src={branding.logoUrl}
alt={name}
width={size === 'hero' ? 214 : 88}
height={size === 'hero' ? 64 : 22}
className={cn('w-auto', size === 'hero' ? 'h-16' : 'h-[22px]')}
priority={size === 'hero'}
// Bypass the image optimizer: its remote-host allowlist is fixed at
// build time, which the runtime-configured Docker image cannot
// satisfy (issue #2203, same reasoning as BrandHomeLink).
unoptimized
/>
</span>
)
}
return (
<span
className={cn(
'font-display tracking-tight inline-block',
size === 'hero' ? 'text-5xl md:text-6xl' : 'text-base',
className,
)}
style={{ fontWeight: 700 }}
>
{name}
</span>
)
}