fix(ui): remove blocking error screens, show progressive loading with skeletons

- Remove full-screen error messages in Explore and Me
- Show skeletons while loading if no data cached
- Display empty states with 'Pull to refresh!' message
- Allow users to pull-to-refresh to retry on errors
- Keep content visible as data streams in progressively
This commit is contained in:
Gigi
2025-10-15 09:34:46 +02:00
parent 5b2ee94062
commit f16c1720a6
2 changed files with 74 additions and 102 deletions

View File

@@ -195,56 +195,28 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
.filter(hasContentOrUrl)
.sort((a, b) => ((b.added_at || 0) - (a.added_at || 0)) || ((b.created_at || 0) - (a.created_at || 0)))
// Only show full loading screen if we don't have any data yet
// Show content progressively - no blocking error screens
const hasData = highlights.length > 0 || bookmarks.length > 0 || readArticles.length > 0 || writings.length > 0
if (loading && !hasData) {
return (
<div className="explore-container" aria-busy="true">
{viewingPubkey && (
<div className="explore-header">
<AuthorCard authorPubkey={viewingPubkey} />
</div>
)}
<div className="explore-grid">
{activeTab === 'writings' ? (
Array.from({ length: 6 }).map((_, i) => (
<BlogPostSkeleton key={i} />
))
) : activeTab === 'highlights' ? (
Array.from({ length: 8 }).map((_, i) => (
<HighlightSkeleton key={i} />
))
) : (
Array.from({ length: 6 }).map((_, i) => (
<BookmarkSkeleton key={i} viewMode={viewMode} />
))
)}
</div>
</div>
)
}
if (error) {
return (
<div className="explore-container">
<div className="explore-error">
<FontAwesomeIcon icon={faExclamationCircle} size="2x" />
<p>{error}</p>
</div>
</div>
)
}
const showSkeletons = loading && !hasData
const renderTabContent = () => {
switch (activeTab) {
case 'highlights':
if (showSkeletons) {
return (
<div className="explore-grid">
{Array.from({ length: 8 }).map((_, i) => (
<HighlightSkeleton key={i} />
))}
</div>
)
}
return highlights.length === 0 ? (
<div className="explore-empty">
<div className="explore-empty" style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-secondary)' }}>
<p>
{isOwnProfile
? 'No highlights yet. Start highlighting content to see them here!'
: 'No highlights yet. You should shame them on nostr!'}
? 'No highlights yet. Pull to refresh!'
: 'No highlights yet. Pull to refresh!'}
</p>
</div>
) : (
@@ -261,9 +233,20 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
)
case 'reading-list':
if (showSkeletons) {
return (
<div className="bookmarks-list">
<div className={`bookmarks-grid bookmarks-${viewMode}`}>
{Array.from({ length: 6 }).map((_, i) => (
<BookmarkSkeleton key={i} viewMode={viewMode} />
))}
</div>
</div>
)
}
return allIndividualBookmarks.length === 0 ? (
<div className="explore-empty">
<p>No bookmarks yet. Bookmark articles to see them here!</p>
<div className="explore-empty" style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-secondary)' }}>
<p>No bookmarks yet. Pull to refresh!</p>
</div>
) : (
<div className="bookmarks-list">
@@ -312,9 +295,18 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
)
case 'archive':
if (showSkeletons) {
return (
<div className="explore-grid">
{Array.from({ length: 6 }).map((_, i) => (
<BlogPostSkeleton key={i} />
))}
</div>
)
}
return readArticles.length === 0 ? (
<div className="explore-empty">
<p>No read articles yet. Mark articles as read to see them here!</p>
<div className="explore-empty" style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-secondary)' }}>
<p>No read articles yet. Pull to refresh!</p>
</div>
) : (
<div className="explore-grid">
@@ -329,25 +321,21 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
)
case 'writings':
if (showSkeletons) {
return (
<div className="explore-grid">
{Array.from({ length: 6 }).map((_, i) => (
<BlogPostSkeleton key={i} />
))}
</div>
)
}
return writings.length === 0 ? (
<div className="explore-empty">
<div className="explore-empty" style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-secondary)' }}>
<p>
{isOwnProfile
? 'No articles written yet. Publish your first article to see it here!'
: (
<>
No articles written. You can find other stuff from this user using{' '}
<a
href={viewingPubkey ? getProfileUrl(nip19.npubEncode(viewingPubkey)) : '#'}
target="_blank"
rel="noopener noreferrer"
style={{ color: 'rgb(99 102 241)', textDecoration: 'underline' }}
>
ants
</a>
.
</>
)}
? 'No articles written yet. Pull to refresh!'
: 'No articles written yet. Pull to refresh!'}
</p>
</div>
) : (