From 6aa9da3df39e768a87037d5d4bb9d6f981728714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Sat, 31 Jan 2026 14:58:30 +0100 Subject: [PATCH] feat: Replace user avatar with Facehash faces (#86) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Replaced the default initials-based avatar fallback with [Facehash](https://facehash.dev/) — deterministic, unique avatar faces generated from the user's name - Same name always produces the same face, no API calls needed - Fully rounded with `rounded-full` styling ## Preview ![Facehash avatars](https://facehash.dev/og-image.png) Each user gets a unique, consistent face based on their name — no more generic initials. ## Changes - Installed `facehash` package - Updated `resources/js/components/user-info.tsx` to use `` instead of `` with initials fallback ## Test plan - [x] Verify avatar renders correctly in the sidebar footer (desktop) - [x] Verify avatar renders correctly in the mobile header - [x] Verify avatar renders correctly in the user dropdown menu - [x] Confirm the same user always gets the same face - [x] Check dark mode appearance --- bun.lock | 3 ++ package.json | 1 + resources/js/components/user-info.tsx | 45 +++++++++++++++++++++------ 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/bun.lock b/bun.lock index 371feaf1..2907947f 100644 --- a/bun.lock +++ b/bun.lock @@ -36,6 +36,7 @@ "date-fns": "^4.1.0", "dexie": "^4.2.1", "dexie-react-hooks": "^4.2.0", + "facehash": "^0.0.5", "globals": "^15.14.0", "input-otp": "^1.4.2", "json-logic-js": "^2.0.5", @@ -1029,6 +1030,8 @@ "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + "facehash": ["facehash@0.0.5", "", { "peerDependencies": { "@types/react": "", "next": ">=15", "react": ">=18 <20", "react-dom": ">=18 <20" }, "optionalPeers": ["@types/react", "next"] }, "sha512-txM9veJ9IPNWsimpHD+Qqr5aFcSdgJPBY2oTRzRgIfSPcLxwZQY4++7vv9fHjco25ErUxC9pdq9tPB9NK1UhTQ=="], + "fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], diff --git a/package.json b/package.json index db22f1e0..5bc2391b 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "date-fns": "^4.1.0", "dexie": "^4.2.1", "dexie-react-hooks": "^4.2.0", + "facehash": "^0.0.5", "globals": "^15.14.0", "input-otp": "^1.4.2", "json-logic-js": "^2.0.5", diff --git a/resources/js/components/user-info.tsx b/resources/js/components/user-info.tsx index 364a08cf..49ae9c50 100644 --- a/resources/js/components/user-info.tsx +++ b/resources/js/components/user-info.tsx @@ -1,7 +1,32 @@ -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { useInitials } from '@/hooks/use-initials'; import { cn } from '@/lib/utils'; import { type User } from '@/types'; +import { Facehash } from 'facehash'; +import { Avatar } from './ui/avatar'; + +export const tailwindColorClasses = [ + 'bg-slate-200 dark:bg-slate-800', + 'bg-gray-200 dark:bg-gray-800', + 'bg-zinc-200 dark:bg-zinc-800', + 'bg-neutral-200 dark:bg-neutral-800', + 'bg-stone-200 dark:bg-stone-800', + 'bg-red-200 dark:bg-red-800', + 'bg-orange-200 dark:bg-orange-800', + 'bg-amber-200 dark:bg-amber-800', + 'bg-yellow-200 dark:bg-yellow-800', + 'bg-lime-200 dark:bg-lime-800', + 'bg-green-200 dark:bg-green-800', + 'bg-emerald-200 dark:bg-emerald-800', + 'bg-teal-200 dark:bg-teal-800', + 'bg-cyan-200 dark:bg-cyan-800', + 'bg-sky-200 dark:bg-sky-800', + 'bg-blue-200 dark:bg-blue-800', + 'bg-indigo-200 dark:bg-indigo-800', + 'bg-violet-200 dark:bg-violet-800', + 'bg-purple-200 dark:bg-purple-800', + 'bg-fuchsia-200 dark:bg-fuchsia-800', + 'bg-pink-200 dark:bg-pink-800', + 'bg-rose-200 dark:bg-rose-800', +]; export function UserInfo({ user, @@ -12,16 +37,18 @@ export function UserInfo({ showEmail?: boolean; hideNameOnMobile?: boolean; }) { - const getInitials = useInitials(); - return ( <> - - - - {getInitials(user.name)} - + + +