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:
@@ -13,7 +13,8 @@ import {
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
|
||||
import { UserAvatar } from '@/components/shared/user-avatar'
|
||||
import { getUserAvatarUrl } from '@/server/utils/avatar-url'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -24,7 +25,7 @@ import {
|
||||
} from '@/components/ui/table'
|
||||
import type { Route } from 'next'
|
||||
import { Plus, GraduationCap, Eye } from 'lucide-react'
|
||||
import { formatDate, getInitials } from '@/lib/utils'
|
||||
import { formatDate } from '@/lib/utils'
|
||||
|
||||
async function MentorsContent() {
|
||||
const mentors = await prisma.user.findMany({
|
||||
@@ -41,7 +42,15 @@ async function MentorsContent() {
|
||||
orderBy: [{ status: 'asc' }, { name: 'asc' }],
|
||||
})
|
||||
|
||||
if (mentors.length === 0) {
|
||||
// Generate avatar URLs
|
||||
const mentorsWithAvatars = await Promise.all(
|
||||
mentors.map(async (mentor) => ({
|
||||
...mentor,
|
||||
avatarUrl: await getUserAvatarUrl(mentor.profileImageKey, mentor.profileImageProvider),
|
||||
}))
|
||||
)
|
||||
|
||||
if (mentorsWithAvatars.length === 0) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
||||
@@ -83,15 +92,11 @@ async function MentorsContent() {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{mentors.map((mentor) => (
|
||||
{mentorsWithAvatars.map((mentor) => (
|
||||
<TableRow key={mentor.id}>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="h-8 w-8">
|
||||
<AvatarFallback className="text-xs">
|
||||
{getInitials(mentor.name || mentor.email || 'M')}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<UserAvatar user={mentor} avatarUrl={mentor.avatarUrl} size="sm" />
|
||||
<div>
|
||||
<p className="font-medium">{mentor.name || 'Unnamed'}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
@@ -150,16 +155,12 @@ async function MentorsContent() {
|
||||
|
||||
{/* Mobile card view */}
|
||||
<div className="space-y-4 md:hidden">
|
||||
{mentors.map((mentor) => (
|
||||
{mentorsWithAvatars.map((mentor) => (
|
||||
<Card key={mentor.id}>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="h-10 w-10">
|
||||
<AvatarFallback>
|
||||
{getInitials(mentor.name || mentor.email || 'M')}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<UserAvatar user={mentor} avatarUrl={mentor.avatarUrl} size="md" />
|
||||
<div>
|
||||
<CardTitle className="text-base">
|
||||
{mentor.name || 'Unnamed'}
|
||||
|
||||
Reference in New Issue
Block a user