timconspicuous.neocities.org
at main 161 lines 4.1 kB view raw
1export default async function ReadingProgress() { 2 try { 3 // Fetch data at build time 4 const bookData = await fetchReadingProgress(); 5 6 if (!bookData) { 7 return renderNoBooks(); 8 } 9 10 return ( 11 <div 12 class="reading-progress-container" 13 dangerouslySetInnerHTML={{ 14 __html: renderBookProgress(bookData), 15 }} 16 /> 17 ); 18 } catch (error) { 19 return ( 20 <div 21 class="reading-progress-container reading-progress-error" 22 dangerouslySetInnerHTML={{ 23 __html: renderError( 24 error instanceof Error 25 ? error.message 26 : "Unknown error", 27 ), 28 }} 29 /> 30 ); 31 } 32} 33 34// Helper functions (same as before) 35async function fetchReadingProgress() { 36 try { 37 const progressResponse = await fetch( 38 "https://pds.timtinkers.online/xrpc/com.atproto.repo.listRecords?repo=did%3Aplc%3Ao6xucog6fghiyrvp7pyqxcs3&collection=social.popfeed.feed.listItem", 39 ); 40 41 if (!progressResponse.ok) { 42 throw new Error(`API request failed: ${progressResponse.status}`); 43 } 44 45 const data = await progressResponse.json(); 46 47 const booksWithProgress = data.records.filter( 48 (record: any) => 49 record.value.bookProgress && 50 record.value.bookProgress.updatedAt, 51 ); 52 53 if (booksWithProgress.length === 0) { 54 return null; 55 } 56 57 const mostRecent = booksWithProgress.reduce( 58 (latest: any, current: any) => { 59 const latestDate = new Date(latest.value.updatedAt); 60 const currentDate = new Date(current.value.updatedAt); 61 return currentDate > latestDate ? current : latest; 62 }, 63 ); 64 65 const progress = { 66 isbn13: mostRecent.value.identifiers.isbn13, 67 progress: mostRecent.value.bookProgress.percent, 68 updatedAt: mostRecent.value.bookProgress.updatedAt, 69 totalPages: mostRecent.value.bookProgress.totalPages, 70 currentPage: mostRecent.value.bookProgress.currentPage, 71 }; 72 73 const metadata = await fetchMetadata(progress.isbn13); 74 75 return { ...progress, ...metadata }; 76 } catch (error) { 77 throw new Error( 78 `Failed to fetch reading progress: ${(error as Error).message}`, 79 ); 80 } 81} 82 83async function fetchMetadata(isbn13: string) { 84 const response = await fetch( 85 `https://openlibrary.org/api/books?bibkeys=ISBN:${isbn13}&format=json&jscmd=data`, 86 ); 87 88 if (!response.ok) { 89 throw new Error(`API request failed: ${response.status}`); 90 } 91 92 const data = await response.json(); 93 const metadata = Object.values(data)[0] as any; 94 95 return { 96 title: metadata.title, 97 author: metadata.authors[0]?.name, 98 coverUrl: metadata.cover?.medium, 99 }; 100} 101 102function formatDate(dateString: string): string { 103 return new Date(dateString).toLocaleDateString("en-US", { 104 month: "short", 105 day: "numeric", 106 year: "numeric", 107 }); 108} 109 110function renderNoBooks(): string { 111 return ` 112 <div class="reading-progress-empty"> 113 <p>📚 No books currently in progress</p> 114 </div> 115 `; 116} 117 118function renderError(message: string): string { 119 return ` 120 <p>📚 Unable to load current reading progress</p> 121 <small>${message}</small> 122 `; 123} 124 125function renderBookProgress(book: any): string { 126 const coverImage = book.coverUrl 127 ? `<img src="${book.coverUrl}" alt="Book cover for ${book.title}" class="book-cover" />` 128 : '<div class="book-cover-placeholder">📖</div>'; 129 130 return ` 131 <div class="reading-progress-header"> 132 <span>📚</span> Currently Reading 133 </div> 134 <div class="book-info"> 135 ${coverImage} 136 <div class="book-details"> 137 <div class="book-title"> 138 ${book.title} 139 </div> 140 <div class="book-author"> 141 by ${book.author} 142 </div> 143 <div class="book-meta"> 144 <span class="progress-badge">In progress</span> 145 <span class="last-updated"> 146 Updated ${formatDate(book.updatedAt)} 147 </span> 148 </div> 149 </div> 150 </div> 151 <div class="progress-container"> 152 <div class="progress-bar"> 153 <div class="progress-fill" style="width: ${book.progress}%"></div> 154 </div> 155 <div class="progress-details"> 156 <span class="progress-percent">${book.progress}%</span> 157 <span class="progress-pages">${book.currentPage} / ${book.totalPages} pages</span> 158 </div> 159 </div> 160 `; 161}