timconspicuous.neocities.org
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}