Files
log/oauth/AVATAR_USAGE_EXAMPLES.md
2025-06-19 13:09:37 +09:00

8.9 KiB

Avatar System Usage Examples

This document provides practical examples of how to use the avatar fetching system in your components.

Basic Usage

Simple Avatar Display

import Avatar from './components/Avatar.jsx'

function UserProfile({ user }) {
  return (
    <div className="user-profile">
      <Avatar
        handle={user.handle}
        did={user.did}
        size={80}
        alt={`${user.displayName}'s avatar`}
      />
      <h3>{user.displayName}</h3>
    </div>
  )
}

Avatar from Record Data

function CommentItem({ record }) {
  return (
    <div className="comment">
      <Avatar
        record={record}
        size={40}
        showFallback={true}
      />
      <div className="comment-content">
        <strong>{record.value.author.displayName}</strong>
        <p>{record.value.text}</p>
      </div>
    </div>
  )
}

Avatar with Hover Card

import { AvatarWithCard } from './components/Avatar.jsx'

function UserList({ users, apiConfig }) {
  return (
    <div className="user-list">
      {users.map(user => (
        <AvatarWithCard
          key={user.handle}
          handle={user.handle}
          did={user.did}
          displayName={user.displayName}
          apiConfig={apiConfig}
          size={50}
        />
      ))}
    </div>
  )
}

Advanced Usage

Programmatic Avatar Fetching

import { useEffect, useState } from 'react'
import { getAvatar, batchFetchAvatars } from './utils/avatar.js'

function useUserAvatars(users) {
  const [avatars, setAvatars] = useState(new Map())
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    async function fetchAvatars() {
      setLoading(true)
      try {
        const avatarMap = await batchFetchAvatars(users)
        setAvatars(avatarMap)
      } catch (error) {
        console.error('Failed to fetch avatars:', error)
      } finally {
        setLoading(false)
      }
    }

    if (users.length > 0) {
      fetchAvatars()
    }
  }, [users])

  return { avatars, loading }
}

// Usage
function TeamDisplay({ team }) {
  const { avatars, loading } = useUserAvatars(team.members)
  
  if (loading) return <div>Loading team...</div>
  
  return (
    <div className="team">
      {team.members.map(member => (
        <img 
          key={member.handle}
          src={avatars.get(member.handle) || '/default-avatar.png'}
          alt={member.displayName}
        />
      ))}
    </div>
  )
}

Force Refresh Avatar

import { useState } from 'react'
import Avatar from './components/Avatar.jsx'
import { getAvatar, clearAvatarCache } from './utils/avatar.js'

function RefreshableAvatar({ handle, did }) {
  const [key, setKey] = useState(0)
  
  const handleRefresh = async () => {
    // Clear cache for this user
    clearAvatarCache(handle)
    
    // Force re-render of Avatar component
    setKey(prev => prev + 1)
    
    // Optionally, prefetch fresh avatar
    try {
      await getAvatar({ handle, did, forceFresh: true })
    } catch (error) {
      console.error('Failed to refresh avatar:', error)
    }
  }
  
  return (
    <div className="refreshable-avatar">
      <Avatar 
        key={key}
        handle={handle}
        did={did}
        size={60}
      />
      <button onClick={handleRefresh}>
        Refresh Avatar
      </button>
    </div>
  )
}

Avatar List with Overflow

import { AvatarList } from './components/Avatar.jsx'

function ParticipantsList({ participants, maxVisible = 5 }) {
  return (
    <div className="participants">
      <h4>Participants ({participants.length})</h4>
      <AvatarList 
        users={participants}
        maxDisplay={maxVisible}
        size={32}
      />
      {participants.length > maxVisible && (
        <span className="overflow-text">
          and {participants.length - maxVisible} more...
        </span>
      )}
    </div>
  )
}

Error Handling

Custom Error Handling

import { useState } from 'react'
import Avatar from './components/Avatar.jsx'

function RobustAvatar({ handle, did, fallbackSrc }) {
  const [hasError, setHasError] = useState(false)
  
  const handleError = (error) => {
    console.warn(`Avatar failed for ${handle}:`, error)
    setHasError(true)
  }
  
  if (hasError && fallbackSrc) {
    return (
      <img 
        src={fallbackSrc}
        alt="Fallback avatar"
        className="avatar"
        onError={() => setHasError(false)} // Reset on fallback error
      />
    )
  }
  
  return (
    <Avatar
      handle={handle}
      did={did}
      onError={handleError}
      showFallback={!hasError}
    />
  )
}

Loading States

import { useState, useEffect } from 'react'
import { getAvatar } from './utils/avatar.js'

function AvatarWithCustomLoading({ handle, did }) {
  const [avatar, setAvatar] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
  
  useEffect(() => {
    async function loadAvatar() {
      try {
        setLoading(true)
        setError(null)
        const avatarUrl = await getAvatar({ handle, did })
        setAvatar(avatarUrl)
      } catch (err) {
        setError(err.message)
      } finally {
        setLoading(false)
      }
    }
    
    loadAvatar()
  }, [handle, did])
  
  if (loading) {
    return <div className="avatar-loading-spinner">Loading...</div>
  }
  
  if (error) {
    return <div className="avatar-error">Failed to load avatar</div>
  }
  
  if (!avatar) {
    return <div className="avatar-placeholder">No avatar</div>
  }
  
  return <img src={avatar} alt="Avatar" className="avatar" />
}

Optimization Patterns

Preloading Strategy

import { useEffect } from 'react'
import { prefetchAvatar } from './utils/avatar.js'

function UserCard({ user, isVisible }) {
  // Preload avatar when component becomes visible
  useEffect(() => {
    if (isVisible && user.handle) {
      prefetchAvatar(user.handle)
    }
  }, [isVisible, user.handle])
  
  return (
    <div className="user-card">
      {isVisible && (
        <Avatar handle={user.handle} did={user.did} />
      )}
      <h4>{user.displayName}</h4>
    </div>
  )
}

Lazy Loading with Intersection Observer

import { useState, useEffect, useRef } from 'react'
import Avatar from './components/Avatar.jsx'

function LazyAvatar({ handle, did, ...props }) {
  const [isVisible, setIsVisible] = useState(false)
  const ref = useRef()
  
  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true)
          observer.disconnect()
        }
      },
      { threshold: 0.1 }
    )
    
    if (ref.current) {
      observer.observe(ref.current)
    }
    
    return () => observer.disconnect()
  }, [])
  
  return (
    <div ref={ref}>
      {isVisible ? (
        <Avatar handle={handle} did={did} {...props} />
      ) : (
        <div className="avatar-placeholder" style={{ 
          width: props.size || 40, 
          height: props.size || 40 
        }} />
      )}
    </div>
  )
}

Cache Management

Cache Statistics Display

import { useEffect, useState } from 'react'
import { getAvatarCacheStats, cleanupExpiredAvatars } from './utils/avatarCache.js'

function CacheStatsPanel() {
  const [stats, setStats] = useState(null)
  
  useEffect(() => {
    const updateStats = () => {
      setStats(getAvatarCacheStats())
    }
    
    updateStats()
    const interval = setInterval(updateStats, 5000) // Update every 5 seconds
    
    return () => clearInterval(interval)
  }, [])
  
  const handleCleanup = async () => {
    const cleaned = cleanupExpiredAvatars()
    alert(`Cleaned ${cleaned} expired cache entries`)
    setStats(getAvatarCacheStats())
  }
  
  if (!stats) return null
  
  return (
    <div className="cache-stats">
      <h4>Avatar Cache Stats</h4>
      <p>Cached avatars: {stats.totalCached}</p>
      <p>Cache hit rate: {stats.hitRate}%</p>
      <p>Cache hits: {stats.cacheHits}</p>
      <p>Cache misses: {stats.cacheMisses}</p>
      <button onClick={handleCleanup}>
        Clean Expired Cache
      </button>
    </div>
  )
}

Testing Helpers

Mock Avatar for Testing

// For testing environments
const MockAvatar = ({ handle, size = 40, showFallback = true }) => {
  if (!showFallback) return null
  
  const initial = (handle || 'U')[0].toUpperCase()
  
  return (
    <div 
      className="avatar-mock"
      style={{
        width: size,
        height: size,
        borderRadius: '50%',
        backgroundColor: '#e1e1e1',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: size * 0.4,
        color: '#666'
      }}
    >
      {initial}
    </div>
  )
}

// Use in tests
export default process.env.NODE_ENV === 'test' ? MockAvatar : Avatar

These examples demonstrate the flexibility and power of the avatar system while maintaining good performance and user experience practices.