Aller au contenu
login
arrow_backRetour aux issues
ansvisor/ansvisor #759

feat: let a brand set its own logo URL, and fall back to the site's favicon when Google has none

ecoDébutant enhancement good first issue help wanted

descriptionDescription

### Problem / motivation A brand's icon comes from exactly one place. [`web/src/lib/favicon.ts`](https://github.com/ansvisor/ansvisor/blob/main/web/src/lib/favicon.ts) builds a URL pointing at Google's `s2/favicons` service, and that URL is stored as `brands.logo_url`. When Google has no icon for the domain, the brand has no icon — and there is nothing the user can do about it. This is not rare — two brands added on the same day both landed on it. Both of those sites publish a perfectly good icon of their own; one serves a 180×180 `apple-touch-icon.png` with the actual logo, and Google simply hasn't indexed it. DuckDuckGo's equivalent service returns `404` for the same two domains, so swapping providers is not the answer either. The second half of the problem is that the value is **derived on every save**. In [`brands/[id]/settings/page.tsx`](https://github.com/ansvisor/ansvisor/blob/main/web/src/app/%5Blocale%5D/(dashboard)/dashboard/brands/%5Bid%5D/settings/page.tsx): ```ts const newLogoUrl = newPrimary ? getFaviconUrl(newPrimary.domain) : null; ``` So even correcting the row by hand is temporary — changing the primary domain recomputes it and the correction is gone. ### Proposed solution Two parts. They are one issue because they touch the same value and the same call sites, but they can land in either order. **1. Let a brand carry its own logo URL** Add a logo URL field to brand settings. A plain URL text input — **no file upload and no storage bucket**, deliberately. `brands.logo_url` already exists and already holds a URL, so there is no migration. When the field has a value, it is used and it **sticks**: saving domains must not overwrite it. The derived favicon is only a default for when the field is empty. **2. Fall back to the site's own favicon when Google has nothing** When the Google URL fails to load, try `https:///favicon.ico` before giving up and showing the initials. Do this **at render time**, not by storing a second URL. The fallback is presentational; keeping it out of the database means `logo_url` continues to mean "the icon this brand chose" and no backfill is ever needed. The natural shape is one small component — something like `BrandAvatar` — that walks the chain *manual URL → Google favicon → site `/favicon.ico` → initials* and is used everywhere a brand avatar appears today: - [`components/layout/brand-switcher.tsx`](https://github.com/ansvisor/ansvisor/blob/main/web/src/components/layout/brand-switcher.tsx) (two places) - [`components/dashboard/brand-card.tsx`](https://github.com/ansvisor/ansvisor/blob/main/web/src/components/dashboard/brand-card.tsx) - [`components/providers/brand-guard.tsx`](https://github.com/ansvisor/ansvisor/blob/main/web/src/components/providers/brand-guard.tsx) - [`brands/[id]/layout.tsx`](https://github.com/ansvisor/ansvisor/blob/main/web/src/app/%5Blocale%5D/(dashboard)/dashboard/brands/%5Bid%5D/layout.tsx) **Notes** - Radix's `AvatarImage` already reports a failed load, which is the hook the chain needs — you should not have to fetch anything yourself. - Google answers a miss with **`404` carrying a valid PNG body** (the grey globe). Browsers will often render that rather than firing an error, so treating "image loaded" as success is not enough on its own. Check what actually happens for a known-missing domain before assuming which event fires. - Don't validate the pasted URL by fetching it server-side. If it doesn't load, the chain already ends at the initials. - `team-section.tsx` also uses `AvatarImage`, for member avatars. Leave it alone — different data, different fallback. ### Alternatives considered **Upload the logo to storage.** The most robust option and explicitly not wanted: it drags in a bucket, size and format validation, and cleanup. A URL field solves the same problem with a text input. **Fetch and cache the icon ourselves at brand creation.** Same objection, plus it makes brand creation depend on a third-party fetch succeeding.
codeOuvre sur GitHub