oauth_new
This commit is contained in:
420
oauth_new/AVATAR_USAGE_EXAMPLES.md
Normal file
420
oauth_new/AVATAR_USAGE_EXAMPLES.md
Normal file
@ -0,0 +1,420 @@
|
||||
# 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
|
||||
|
||||
```jsx
|
||||
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
|
||||
|
||||
```jsx
|
||||
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
|
||||
|
||||
```jsx
|
||||
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
|
||||
|
||||
```jsx
|
||||
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
|
||||
|
||||
```jsx
|
||||
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
|
||||
|
||||
```jsx
|
||||
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
|
||||
|
||||
```jsx
|
||||
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
|
||||
|
||||
```jsx
|
||||
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
|
||||
|
||||
```jsx
|
||||
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
|
||||
|
||||
```jsx
|
||||
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
|
||||
|
||||
```jsx
|
||||
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
|
||||
|
||||
```jsx
|
||||
// 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.
|
Reference in New Issue
Block a user