Trying to figure out avatar urls. Not easy.

This commit is contained in:
2025-06-13 19:19:52 -05:00
parent 7e755535fe
commit 0e62bafa45
3 changed files with 119 additions and 7 deletions

View File

@ -5,17 +5,28 @@ import { useState, useEffect, useCallback, useRef } from 'react';
import { useAuth, useTVMode } from '@/components/context';
import {
getRecentUsersWithStatuses,
getSignedUrl,
updateStatuses,
updateUserStatus,
type UserWithStatus,
} from '@/lib/hooks';
import { Drawer, DrawerTrigger, Loading } from '@/components/ui';
import {
Avatar,
AvatarImage,
AvatarFallback,
BasedAvatarImage,
BasedAvatarFallback,
Drawer,
DrawerTrigger,
Loading
} from '@/components/ui';
import { SubmitButton } from '@/components/default';
import { toast } from 'sonner';
import { HistoryDrawer } from '@/components/status';
import type { Profile } from '@/utils/supabase';
import type { RealtimeChannel } from '@supabase/supabase-js';
import { makeConditionalClassName } from '@/lib/utils';
import { User } from 'lucide-react';
type TechTableProps = {
initialStatuses: UserWithStatus[];
@ -47,6 +58,22 @@ export const TechTable = ({
}
}, []);
const fetchAvatarUrl = useCallback(async (url: string) => {
try {
const avatarResponse = await getSignedUrl({
bucket: 'avatars',
url,
transform: { width: 128, height: 128 },
});
if (!avatarResponse.success) {
throw new Error(avatarResponse.error);
}
return avatarResponse.data;
} catch (error) {
console.error('Error fetching avatar URL:', error);
}
}, []);
// Initial load
useEffect(() => {
const loadData = async () => {
@ -225,7 +252,7 @@ export const TechTable = ({
/>
</th>
)}
<th className={thClassName}>Name</th>
<th className={thClassName}>Technician</th>
<th className={thClassName}>
<Drawer>
<DrawerTrigger
@ -261,7 +288,25 @@ export const TechTable = ({
</td>
)}
<td className={tdClassName}>
{userWithStatus.user.full_name ?? 'Unknown User'}
<div className='flex'>
<Avatar>
{userWithStatus.user.avatar_url ? (
<BasedAvatarImage
src={userWithStatus.avatar_url}
fullName={userWithStatus.user.full_name ?? ''}
width={64}
height={64}
/>
): (
<BasedAvatarFallback
className='text-md'
fullName={userWithStatus.user.full_name}
/>
)}
</Avatar>
<p>{userWithStatus.user.full_name ?? 'Unknown User'}</p>
<p>{userWithStatus.avatar_url}</p>
</div>
</td>
<td className={tdClassName}>
<Drawer>

View File

@ -2,6 +2,7 @@
import * as React from 'react';
import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { User } from 'lucide-react';
import { cn } from '@/lib/utils';
@ -21,6 +22,25 @@ function Avatar({
);
}
type BasedAvatarImageProps = React.ComponentProps<typeof AvatarPrimitive.Image> & {
fullName: string;
};
function BasedAvatarImage({
fullName,
className,
...props
}: BasedAvatarImageProps) {
return (
<AvatarPrimitive.Image
data-slot='avatar-image'
className={cn('aspect-square size-full', className)}
alt={fullName.split(' ').map((n) => n[0]).join('').toUpperCase()}
{...props}
/>
);
}
function AvatarImage({
className,
...props
@ -34,10 +54,42 @@ function AvatarImage({
);
}
type BasedAvatarFallbackProps =
React.ComponentProps<typeof AvatarPrimitive.Fallback> & {
fullName?: string | null;
};
function BasedAvatarFallback({
fullName = null,
className,
...props
}: BasedAvatarFallbackProps) {
return (
<AvatarPrimitive.Fallback
data-slot='avatar-fallback'
className={cn(
'bg-muted flex size-full items-center justify-center rounded-full',
className,
)}
{...props}
>
{fullName ? (
fullName
.split(' ')
.map((n) => n[0])
.join('')
.toUpperCase()
) : (
<User size={64} />
)}
</AvatarPrimitive.Fallback>
);
}
function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
}: React.ComponentProps<typeof AvatarPrimitive.Fallback & {fullName: string}>) {
return (
<AvatarPrimitive.Fallback
data-slot='avatar-fallback'
@ -50,4 +102,4 @@ function AvatarFallback({
);
}
export { Avatar, AvatarImage, AvatarFallback };
export { Avatar, AvatarImage, BasedAvatarImage, AvatarFallback, BasedAvatarFallback };

View File

@ -1,12 +1,13 @@
'use client';
import { createClient } from '@/utils/supabase';
import type { Profile, Result } from '@/utils/supabase';
import { getUser, getProfile } from '@/lib/hooks';
import { getUser, getProfile, getSignedUrl } from '@/lib/hooks';
export type UserWithStatus = {
id?: string;
user: Profile;
status: string;
avatar_url?: string;
created_at: string;
updated_by?: Profile;
};
@ -53,7 +54,21 @@ export const getRecentUsersWithStatuses = async (): Promise<
return true;
});
return { success: true, data: filtered };
const filteredWithAvatars: UserWithStatus[] = filtered;
for (let userWithStatus of filteredWithAvatars) {
if (!userWithStatus.user.avatar_url) continue;
const avatarResponse = await getSignedUrl({
bucket: 'avatars',
url: userWithStatus.user.avatar_url,
transform: { width: 128, height: 128 },
});
if (!avatarResponse.success) continue;
else userWithStatus = { ...userWithStatus, avatar_url: avatarResponse.data };
}
console.log('filteredWithAvatars', filteredWithAvatars);
return { success: true, data: filteredWithAvatars };
} catch (error) {
return { success: false, error: `Error: ${error as Error}` };
}