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

@@ -0,0 +1,35 @@
import { createStorageProvider, type StorageProviderType } from '@/lib/storage'
/**
* Generate a pre-signed download URL for a user's avatar.
* Returns null if the user has no avatar.
*/
export async function getUserAvatarUrl(
profileImageKey: string | null | undefined,
profileImageProvider: string | null | undefined
): Promise<string | null> {
if (!profileImageKey) return null
try {
const providerType = (profileImageProvider as StorageProviderType) || 's3'
const provider = createStorageProvider(providerType)
return await provider.getDownloadUrl(profileImageKey)
} catch {
return null
}
}
/**
* Batch-generate avatar URLs for multiple users.
* Adds `avatarUrl` field to each user object.
*/
export async function attachAvatarUrls<
T extends { profileImageKey?: string | null; profileImageProvider?: string | null }
>(users: T[]): Promise<(T & { avatarUrl: string | null })[]> {
return Promise.all(
users.map(async (user) => ({
...user,
avatarUrl: await getUserAvatarUrl(user.profileImageKey, user.profileImageProvider),
}))
)
}