mirror of
https://github.com/dergigi/boris.git
synced 2025-12-20 16:14:20 +01:00
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:
@@ -316,9 +316,18 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
|
|||||||
const renderTabContent = () => {
|
const renderTabContent = () => {
|
||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
case 'writings':
|
case 'writings':
|
||||||
|
if (showSkeletons) {
|
||||||
|
return (
|
||||||
|
<div className="explore-grid">
|
||||||
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
|
<BlogPostSkeleton key={i} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
return filteredBlogPosts.length === 0 ? (
|
return filteredBlogPosts.length === 0 ? (
|
||||||
<div className="explore-empty" style={{ gridColumn: '1/-1', textAlign: 'center', color: 'var(--text-secondary)' }}>
|
<div className="explore-empty" style={{ gridColumn: '1/-1', textAlign: 'center', color: 'var(--text-secondary)', padding: '2rem' }}>
|
||||||
<p>No blog posts found yet.</p>
|
<p>No blog posts yet. Pull to refresh!</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="explore-grid">
|
<div className="explore-grid">
|
||||||
@@ -333,9 +342,18 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
|
|||||||
)
|
)
|
||||||
|
|
||||||
case 'highlights':
|
case 'highlights':
|
||||||
|
if (showSkeletons) {
|
||||||
|
return (
|
||||||
|
<div className="explore-grid">
|
||||||
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
|
<HighlightSkeleton key={i} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
return classifiedHighlights.length === 0 ? (
|
return classifiedHighlights.length === 0 ? (
|
||||||
<div className="explore-empty" style={{ gridColumn: '1/-1', textAlign: 'center', color: 'var(--text-secondary)' }}>
|
<div className="explore-empty" style={{ gridColumn: '1/-1', textAlign: 'center', color: 'var(--text-secondary)', padding: '2rem' }}>
|
||||||
<p>No highlights yet. Your friends should start highlighting content!</p>
|
<p>No highlights yet. Pull to refresh!</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="explore-grid">
|
<div className="explore-grid">
|
||||||
@@ -355,43 +373,9 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 || blogPosts.length > 0
|
const hasData = highlights.length > 0 || blogPosts.length > 0
|
||||||
|
const showSkeletons = loading && !hasData
|
||||||
if (loading && !hasData) {
|
|
||||||
return (
|
|
||||||
<div className="explore-container" aria-busy="true">
|
|
||||||
<div className="explore-header">
|
|
||||||
<h1>
|
|
||||||
<FontAwesomeIcon icon={faNewspaper} />
|
|
||||||
Explore
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<div className="explore-grid">
|
|
||||||
{activeTab === 'writings' ? (
|
|
||||||
Array.from({ length: 6 }).map((_, i) => (
|
|
||||||
<BlogPostSkeleton key={i} />
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
Array.from({ length: 8 }).map((_, i) => (
|
|
||||||
<HighlightSkeleton key={i} />
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<div className="explore-container">
|
|
||||||
<div className="explore-error">
|
|
||||||
<FontAwesomeIcon icon={faExclamationCircle} size="2x" />
|
|
||||||
<p>{error}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="explore-container">
|
<div className="explore-container">
|
||||||
|
|||||||
@@ -195,56 +195,28 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
|
|||||||
.filter(hasContentOrUrl)
|
.filter(hasContentOrUrl)
|
||||||
.sort((a, b) => ((b.added_at || 0) - (a.added_at || 0)) || ((b.created_at || 0) - (a.created_at || 0)))
|
.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
|
const hasData = highlights.length > 0 || bookmarks.length > 0 || readArticles.length > 0 || writings.length > 0
|
||||||
|
const showSkeletons = loading && !hasData
|
||||||
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 renderTabContent = () => {
|
const renderTabContent = () => {
|
||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
case 'highlights':
|
case 'highlights':
|
||||||
|
if (showSkeletons) {
|
||||||
|
return (
|
||||||
|
<div className="explore-grid">
|
||||||
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
|
<HighlightSkeleton key={i} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
return highlights.length === 0 ? (
|
return highlights.length === 0 ? (
|
||||||
<div className="explore-empty">
|
<div className="explore-empty" style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-secondary)' }}>
|
||||||
<p>
|
<p>
|
||||||
{isOwnProfile
|
{isOwnProfile
|
||||||
? 'No highlights yet. Start highlighting content to see them here!'
|
? 'No highlights yet. Pull to refresh!'
|
||||||
: 'No highlights yet. You should shame them on nostr!'}
|
: 'No highlights yet. Pull to refresh!'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -261,9 +233,20 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
|
|||||||
)
|
)
|
||||||
|
|
||||||
case 'reading-list':
|
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 ? (
|
return allIndividualBookmarks.length === 0 ? (
|
||||||
<div className="explore-empty">
|
<div className="explore-empty" style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-secondary)' }}>
|
||||||
<p>No bookmarks yet. Bookmark articles to see them here!</p>
|
<p>No bookmarks yet. Pull to refresh!</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="bookmarks-list">
|
<div className="bookmarks-list">
|
||||||
@@ -312,9 +295,18 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
|
|||||||
)
|
)
|
||||||
|
|
||||||
case 'archive':
|
case 'archive':
|
||||||
|
if (showSkeletons) {
|
||||||
|
return (
|
||||||
|
<div className="explore-grid">
|
||||||
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
|
<BlogPostSkeleton key={i} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
return readArticles.length === 0 ? (
|
return readArticles.length === 0 ? (
|
||||||
<div className="explore-empty">
|
<div className="explore-empty" style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-secondary)' }}>
|
||||||
<p>No read articles yet. Mark articles as read to see them here!</p>
|
<p>No read articles yet. Pull to refresh!</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="explore-grid">
|
<div className="explore-grid">
|
||||||
@@ -329,25 +321,21 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
|
|||||||
)
|
)
|
||||||
|
|
||||||
case 'writings':
|
case 'writings':
|
||||||
|
if (showSkeletons) {
|
||||||
|
return (
|
||||||
|
<div className="explore-grid">
|
||||||
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
|
<BlogPostSkeleton key={i} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
return writings.length === 0 ? (
|
return writings.length === 0 ? (
|
||||||
<div className="explore-empty">
|
<div className="explore-empty" style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-secondary)' }}>
|
||||||
<p>
|
<p>
|
||||||
{isOwnProfile
|
{isOwnProfile
|
||||||
? 'No articles written yet. Publish your first article to see it here!'
|
? 'No articles written yet. Pull to refresh!'
|
||||||
: (
|
: 'No articles written yet. Pull to refresh!'}
|
||||||
<>
|
|
||||||
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>
|
|
||||||
.
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user