Add image cropping to avatar upload and show avatars platform-wide

- Add react-easy-crop for circular crop + zoom UI on avatar upload
- Create server-side getUserAvatarUrl utility for generating pre-signed URLs
- Update all nav components (admin, jury, mentor, observer) to show user avatars
- Add avatar URLs to user list, mentor list, and project detail API responses
- Replace initials-only avatars with UserAvatar component across admin pages

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-02 13:19:28 +01:00
parent f9f88d68ab
commit 8fda8deded
14 changed files with 346 additions and 140 deletions

View File

@@ -37,6 +37,8 @@ import {
import { getInitials } from '@/lib/utils'
import { Logo } from '@/components/shared/logo'
import { EditionSelector } from '@/components/shared/edition-selector'
import { UserAvatar } from '@/components/shared/user-avatar'
import { trpc } from '@/lib/trpc/client'
interface AdminSidebarProps {
user: {
@@ -125,6 +127,7 @@ const roleLabels: Record<string, string> = {
export function AdminSidebar({ user }: AdminSidebarProps) {
const pathname = usePathname()
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
const { data: avatarUrl } = trpc.avatar.getUrl.useQuery()
const isSuperAdmin = user.role === 'SUPER_ADMIN'
const roleLabel = roleLabels[user.role || ''] || 'User'
@@ -242,10 +245,8 @@ export function AdminSidebar({ user }: AdminSidebarProps) {
<DropdownMenuTrigger asChild>
<button className="group flex w-full items-center gap-3 rounded-xl p-2.5 text-left transition-all duration-200 hover:bg-slate-100 dark:hover:bg-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
{/* Avatar */}
<div className="relative flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-brand-blue text-white shadow-xs transition-transform duration-200 group-hover:scale-[1.02]">
<span className="text-sm font-semibold">
{getInitials(user.name || user.email || 'U')}
</span>
<div className="relative shrink-0">
<UserAvatar user={user} avatarUrl={avatarUrl} size="md" />
{/* Online indicator */}
<div className="absolute -bottom-0.5 -right-0.5 h-3 w-3 rounded-full border-2 border-white bg-emerald-500" />
</div>