feat: add filter buttons to Archive tab

- Create ArchiveFilters component with 5 filter options
- All: Show all archived articles
- To Read: Articles with 0% progress (not started)
- Reading: Articles with progress between 0-95%
- Completed: Articles with 95%+ reading progress
- Marked: Manually marked as read (no position data)
- Filter logic based on reading position data
- Show empty state when no articles match filter
- Matches BookmarkFilters styling and UX pattern
This commit is contained in:
Gigi
2025-10-15 22:30:44 +02:00
parent 5e1146b015
commit 5502d71ac4
2 changed files with 89 additions and 10 deletions

View File

@@ -0,0 +1,39 @@
import React from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faBookOpen, faBookmark, faCheckCircle } from '@fortawesome/free-solid-svg-icons'
import { faBooks } from '../icons/customIcons'
export type ArchiveFilterType = 'all' | 'to-read' | 'reading' | 'completed' | 'marked'
interface ArchiveFiltersProps {
selectedFilter: ArchiveFilterType
onFilterChange: (filter: ArchiveFilterType) => void
}
const ArchiveFilters: React.FC<ArchiveFiltersProps> = ({ selectedFilter, onFilterChange }) => {
const filters: { id: ArchiveFilterType; label: string; icon: typeof faBookOpen }[] = [
{ id: 'all', label: 'All', icon: faBooks },
{ id: 'to-read', label: 'To Read', icon: faBookmark },
{ id: 'reading', label: 'Reading', icon: faBookOpen },
{ id: 'completed', label: 'Completed', icon: faCheckCircle },
{ id: 'marked', label: 'Marked', icon: faCheckCircle }
]
return (
<div className="bookmark-filters">
{filters.map((filter) => (
<button
key={filter.id}
className={`bookmark-filter-btn ${selectedFilter === filter.id ? 'active' : ''}`}
onClick={() => onFilterChange(filter.id)}
>
<FontAwesomeIcon icon={filter.icon} />
<span>{filter.label}</span>
</button>
))}
</div>
)
}
export default ArchiveFilters

View File

@@ -27,6 +27,7 @@ import { groupIndividualBookmarks, hasContent } from '../utils/bookmarkUtils'
import BookmarkFilters, { BookmarkFilterType } from './BookmarkFilters'
import { filterBookmarksByType } from '../utils/bookmarkTypeClassifier'
import { generateArticleIdentifier, loadReadingPosition } from '../services/readingPositionService'
import ArchiveFilters, { ArchiveFilterType } from './ArchiveFilters'
interface MeProps {
relayPool: RelayPool
@@ -53,6 +54,7 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
const [viewMode, setViewMode] = useState<ViewMode>('cards')
const [refreshTrigger, setRefreshTrigger] = useState(0)
const [bookmarkFilter, setBookmarkFilter] = useState<BookmarkFilterType>('all')
const [archiveFilter, setArchiveFilter] = useState<ArchiveFilterType>('all')
const [readingPositions, setReadingPositions] = useState<Map<string, number>>(new Map())
// Update local state when prop changes
@@ -238,10 +240,34 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
const allIndividualBookmarks = bookmarks.flatMap(b => b.individualBookmarks || [])
.filter(hasContent)
// Apply filter
// Apply bookmark filter
const filteredBookmarks = filterBookmarksByType(allIndividualBookmarks, bookmarkFilter)
const groups = groupIndividualBookmarks(filteredBookmarks)
// Apply archive filter
const filteredReadArticles = readArticles.filter(post => {
const position = readingPositions.get(post.event.id)
switch (archiveFilter) {
case 'to-read':
// No position or 0% progress
return !position || position === 0
case 'reading':
// Has some progress but not completed (0 < position < 1)
return position !== undefined && position > 0 && position < 0.95
case 'completed':
// 95% or more read (we consider 95%+ as completed)
return position !== undefined && position >= 0.95
case 'marked':
// Manually marked as read (in archive but no reading position data)
// These are articles that were marked via the emoji reaction
return !position || position === 0
case 'all':
default:
return true
}
})
const sections: Array<{ key: string; title: string; items: IndividualBookmark[] }> = [
{ key: 'private', title: 'Private Bookmarks', items: groups.privateItems },
{ key: 'public', title: 'Public Bookmarks', items: groups.publicItems },
@@ -375,16 +401,30 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
<FontAwesomeIcon icon={faSpinner} spin size="2x" />
</div>
) : (
<div className="explore-grid">
{readArticles.map((post) => (
<BlogPostCard
key={post.event.id}
post={post}
href={getPostUrl(post)}
readingProgress={readingPositions.get(post.event.id)}
<>
{readArticles.length > 0 && (
<ArchiveFilters
selectedFilter={archiveFilter}
onFilterChange={setArchiveFilter}
/>
))}
</div>
)}
{filteredReadArticles.length === 0 ? (
<div className="explore-loading" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '4rem', color: 'var(--text-secondary)' }}>
No articles match this filter.
</div>
) : (
<div className="explore-grid">
{filteredReadArticles.map((post) => (
<BlogPostCard
key={post.event.id}
post={post}
href={getPostUrl(post)}
readingProgress={readingPositions.get(post.event.id)}
/>
))}
</div>
)}
</>
)
case 'writings':