feat: add caching to /me page for faster loading

- Create meCache service to store highlights, bookmarks, and read articles
- Seed Me component from cache on load to avoid empty flash
- Show small spinner while refreshing if cached data is displayed
- Update cache when highlights are deleted
- Only show full loading screen if no cached data is available
- Improves perceived performance similar to /explore page
This commit is contained in:
Gigi
2025-10-13 16:50:43 +02:00
parent 88085c48d2
commit e09be543bc
2 changed files with 89 additions and 3 deletions

View File

@@ -17,6 +17,7 @@ import { BookmarkItem } from './BookmarkItem'
import IconButton from './IconButton'
import { ViewMode } from './Bookmarks'
import { extractUrlsFromContent } from '../services/bookmarkHelpers'
import { getCachedMeData, setCachedMeData, updateCachedHighlights } from '../services/meCache'
import { faBooks } from '../icons/customIcons'
interface MeProps {
@@ -47,6 +48,14 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
setLoading(true)
setError(null)
// Seed from cache if available to avoid empty flash
const cached = getCachedMeData(activeAccount.pubkey)
if (cached) {
setHighlights(cached.highlights)
setBookmarks(cached.bookmarks)
setReadArticles(cached.readArticles)
}
// Fetch highlights and read articles
const [userHighlights, userReadArticles] = await Promise.all([
fetchHighlights(relayPool, activeAccount.pubkey),
@@ -57,12 +66,19 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
setReadArticles(userReadArticles)
// Fetch bookmarks using callback pattern
let fetchedBookmarks: Bookmark[] = []
try {
await fetchBookmarks(relayPool, activeAccount, setBookmarks)
await fetchBookmarks(relayPool, activeAccount, (newBookmarks) => {
fetchedBookmarks = newBookmarks
setBookmarks(newBookmarks)
})
} catch (err) {
console.warn('Failed to load bookmarks:', err)
setBookmarks([])
}
// Update cache with all fetched data
setCachedMeData(activeAccount.pubkey, userHighlights, fetchedBookmarks, userReadArticles)
} catch (err) {
console.error('Failed to load data:', err)
setError('Failed to load data. Please try again.')
@@ -75,7 +91,14 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
}, [relayPool, activeAccount])
const handleHighlightDelete = (highlightId: string) => {
setHighlights(prev => prev.filter(h => h.id !== highlightId))
setHighlights(prev => {
const updated = prev.filter(h => h.id !== highlightId)
// Update cache when highlight is deleted
if (activeAccount) {
updateCachedHighlights(activeAccount.pubkey, updated)
}
return updated
})
}
const getPostUrl = (post: BlogPostPreview) => {
@@ -110,7 +133,10 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
.filter(hasContentOrUrl)
.sort((a, b) => ((b.added_at || 0) - (a.added_at || 0)) || ((b.created_at || 0) - (a.created_at || 0)))
if (loading) {
// Only show full loading screen if we don't have any data yet
const hasData = highlights.length > 0 || bookmarks.length > 0 || readArticles.length > 0
if (loading && !hasData) {
return (
<div className="explore-container">
<div className="explore-loading">
@@ -228,6 +254,12 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
<div className="explore-header">
{activeAccount && <AuthorCard authorPubkey={activeAccount.pubkey} />}
{loading && hasData && (
<div className="explore-loading" style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', padding: '0.5rem 0' }}>
<FontAwesomeIcon icon={faSpinner} spin />
</div>
)}
<div className="me-tabs">
<button
className={`me-tab ${activeTab === 'highlights' ? 'active' : ''}`}

54
src/services/meCache.ts Normal file
View File

@@ -0,0 +1,54 @@
import { Highlight } from '../types/highlights'
import { Bookmark } from '../types/bookmarks'
import { BlogPostPreview } from './exploreService'
export interface MeCache {
highlights: Highlight[]
bookmarks: Bookmark[]
readArticles: BlogPostPreview[]
timestamp: number
}
const meCache = new Map<string, MeCache>() // key: pubkey
export function getCachedMeData(pubkey: string): MeCache | null {
const entry = meCache.get(pubkey)
if (!entry) return null
return entry
}
export function setCachedMeData(
pubkey: string,
highlights: Highlight[],
bookmarks: Bookmark[],
readArticles: BlogPostPreview[]
): void {
meCache.set(pubkey, {
highlights,
bookmarks,
readArticles,
timestamp: Date.now()
})
}
export function updateCachedHighlights(pubkey: string, highlights: Highlight[]): void {
const existing = meCache.get(pubkey)
if (existing) {
meCache.set(pubkey, { ...existing, highlights, timestamp: Date.now() })
}
}
export function updateCachedBookmarks(pubkey: string, bookmarks: Bookmark[]): void {
const existing = meCache.get(pubkey)
if (existing) {
meCache.set(pubkey, { ...existing, bookmarks, timestamp: Date.now() })
}
}
export function updateCachedReadArticles(pubkey: string, readArticles: BlogPostPreview[]): void {
const existing = meCache.get(pubkey)
if (existing) {
meCache.set(pubkey, { ...existing, readArticles, timestamp: Date.now() })
}
}