feat: Improve avatar loading experience by displaying placeholders until images are fully loaded across various components.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
@@ -26,17 +27,33 @@ export function ConnectionCard({
|
||||
connectedAt,
|
||||
onMessage,
|
||||
}: ConnectionCardProps) {
|
||||
const [avatarLoaded, setAvatarLoaded] = useState(false);
|
||||
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
||||
const isPlaceholder = !avatar || avatar === placeholder;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 py-4">
|
||||
{/* Avatar */}
|
||||
<div className="flex-shrink-0">
|
||||
<Image
|
||||
src={avatar}
|
||||
alt={name}
|
||||
width={80}
|
||||
height={80}
|
||||
className="rounded-full object-cover w-[80px] h-[80px]"
|
||||
/>
|
||||
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative">
|
||||
{(!avatar || !avatarLoaded || isPlaceholder) && (
|
||||
<Image
|
||||
src={placeholder}
|
||||
alt={name}
|
||||
width={80}
|
||||
height={80}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
)}
|
||||
{avatar && !isPlaceholder && (
|
||||
<img
|
||||
src={avatar}
|
||||
alt={name}
|
||||
className={`absolute inset-0 w-full h-full object-cover rounded-full transition-opacity duration-200 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
onLoad={() => setAvatarLoaded(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface InvitationCardProps {
|
||||
@@ -40,17 +41,33 @@ export function InvitationCard({
|
||||
onAccept,
|
||||
onIgnore,
|
||||
}: InvitationCardProps) {
|
||||
const [avatarLoaded, setAvatarLoaded] = useState(false);
|
||||
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
||||
const isPlaceholder = !avatar || avatar === placeholder;
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col sm:flex-row items-start sm:items-center gap-4 py-4 ${isProcessing ? 'opacity-50 pointer-events-none' : ''}`}>
|
||||
{/* Avatar */}
|
||||
<div className="flex-shrink-0">
|
||||
<Image
|
||||
src={avatar}
|
||||
alt={name}
|
||||
width={80}
|
||||
height={80}
|
||||
className="rounded-full object-cover w-[80px] h-[80px]"
|
||||
/>
|
||||
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative">
|
||||
{(!avatar || !avatarLoaded || isPlaceholder) && (
|
||||
<Image
|
||||
src={placeholder}
|
||||
alt={name}
|
||||
width={80}
|
||||
height={80}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
)}
|
||||
{avatar && !isPlaceholder && (
|
||||
<img
|
||||
src={avatar}
|
||||
alt={name}
|
||||
className={`absolute inset-0 w-full h-full object-cover rounded-full transition-opacity duration-200 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
onLoad={() => setAvatarLoaded(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
|
||||
@@ -111,9 +111,6 @@ body {
|
||||
}
|
||||
|
||||
/* Smooth scrolling */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Selection styling */
|
||||
::selection {
|
||||
|
||||
@@ -130,7 +130,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} ${sourceSerif4.variable} ${fractul.variable} antialiased`}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user