chore: added mark individual posts as read or unread 12e23777
Steve · 2025-11-02 16:25 1 file(s) · +77 −13
src/components/dashboard.tsx +77 −13
17 17
	SidebarTrigger,
18 18
} from "@/components/ui/sidebar";
19 19
import { Button } from "@/components/ui/button";
20 -
import { ExternalLink } from "lucide-react";
20 +
import { ExternalLink, Circle, CircleCheckBig } from "lucide-react";
21 21
import { useQuery } from "@evolu/react";
22 -
import { allFeedsQuery, allPostsQuery } from "@/lib/evolu";
22 +
import {
23 +
	allFeedsQuery,
24 +
	allPostsQuery,
25 +
	allReadStatusesQuery,
26 +
	allReadStatusesWithUnreadQuery,
27 +
	useEvolu,
28 +
} from "@/lib/evolu";
23 29
import ReactMarkdown from "react-markdown";
24 30
import rehypeSanitize from "rehype-sanitize";
25 31
import rehypeRaw from "rehype-raw";
26 32
import remarkGfm from "remark-gfm";
33 +
import { toast } from "sonner";
27 34
28 35
function Dashboard() {
29 36
	const [selectedFeedId, setSelectedFeedId] = React.useState<string | null>(
34 41
	);
35 42
	const mainContentRef = React.useRef<HTMLDivElement>(null);
36 43
44 +
	const evolu = useEvolu();
37 45
	const allFeeds = useQuery(allFeedsQuery);
38 46
	const allPosts = useQuery(allPostsQuery);
47 +
	const allReadStatuses = useQuery(allReadStatusesQuery);
48 +
	const allReadStatusesWithUnread = useQuery(allReadStatusesWithUnreadQuery);
39 49
	console.log(allPosts);
40 50
41 51
	const selectedFeed = selectedFeedId
46 56
		? allPosts.find((p) => p.id === selectedPostId)
47 57
		: null;
48 58
59 +
	// Check if current post is read
60 +
	const isCurrentPostRead = React.useMemo(() => {
61 +
		if (!selectedPostId) return false;
62 +
		return allReadStatuses.some((status) => status.postId === selectedPostId);
63 +
	}, [selectedPostId, allReadStatuses]);
64 +
65 +
	// Toggle read/unread status for current post
66 +
	const toggleReadStatus = React.useCallback(() => {
67 +
		if (!selectedPostId || !selectedPost) return;
68 +
69 +
		const existingStatus = allReadStatusesWithUnread.find(
70 +
			(status) => status.postId === selectedPostId,
71 +
		);
72 +
73 +
		if (existingStatus) {
74 +
			// Update existing status
75 +
			const newReadStatus = existingStatus.isRead ? 0 : 1;
76 +
			evolu.update("readStatus", {
77 +
				id: existingStatus.id as any,
78 +
				isRead: newReadStatus,
79 +
			});
80 +
			toast.success(newReadStatus ? "Marked as read" : "Marked as unread");
81 +
		} else if (selectedPost.feedId) {
82 +
			// Create new read status (mark as read)
83 +
			evolu.insert("readStatus", {
84 +
				postId: selectedPostId,
85 +
				feedId: selectedPost.feedId,
86 +
				isRead: 1,
87 +
			});
88 +
			toast.success("Marked as read");
89 +
		}
90 +
	}, [selectedPostId, selectedPost, allReadStatusesWithUnread, evolu]);
91 +
49 92
	// Scroll to top when a new post is selected
50 93
	React.useEffect(() => {
51 94
		if (selectedPostId && mainContentRef.current) {
138 181
						{selectedPost ? (
139 182
							<div className="flex flex-col gap-6 max-w-4xl mx-auto w-full pb-8">
140 183
								<div className="flex flex-col gap-3">
141 -
									<h1 className="text-3xl font-bold tracking-tight">
184 +
									<a
185 +
										href={selectedPost.link ? selectedPost.link : ""}
186 +
										target="_blank"
187 +
										rel="noopener noreferrer"
188 +
										className="text-3xl font-bold tracking-tight hover:underline"
189 +
									>
142 190
										{selectedPost.title}
143 -
									</h1>
191 +
									</a>
144 192
									<div className="flex items-center gap-4 text-sm text-muted-foreground">
145 193
										{selectedPost.author && (
146 -
											<span>By {selectedPost.author}</span>
147 -
										)}
148 -
										{selectedPost.link && (
149 -
											<Button variant="outline" size="sm" asChild>
194 +
											<>
195 +
												{" "}
196 +
												by
150 197
												<a
151 -
													href={selectedPost.link}
152 198
													target="_blank"
153 199
													rel="noopener noreferrer"
154 -
													className="flex items-center gap-2"
200 +
													className="hover:underline"
201 +
													href={
202 +
														selectedPost.author && selectedPost.link
203 +
															? getBaseUrl(selectedPost.link)
204 +
															: ""
205 +
													}
155 206
												>
156 -
													<ExternalLink className="h-3 w-3" />
157 -
													Open Original
207 +
													{selectedPost.author}
158 208
												</a>
209 +
											</>
210 +
										)}
211 +
										<div className="flex items-center gap-2">
212 +
											<Button
213 +
												variant="outline"
214 +
												size="sm"
215 +
												onClick={toggleReadStatus}
216 +
												className="flex items-center gap-2"
217 +
											>
218 +
												{isCurrentPostRead ? (
219 +
													<Circle className="h-3 w-3" />
220 +
												) : (
221 +
													<CircleCheckBig className="h-3 w-3" />
222 +
												)}
159 223
											</Button>
160 -
										)}
224 +
										</div>
161 225
									</div>
162 226
									<span className="text-xs text-muted-foreground">
163 227
										{selectedPost.publishedDate