Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions app/api/posts/view/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { doc, getDoc, setDoc } from "firebase/firestore";
import { getServerDb } from "../../../../lib/firebase-admin";

export async function POST(req) {
try {
const { userId, postId } = await req.json();

if (!userId || !postId) {
return Response.json(
{ error: "userId and postId are required" },
{ status: 400 }
);
}

const db = getServerDb();

// Verify the post exists
const postRef = doc(db, "posts", postId);
const postSnap = await getDoc(postRef);
if (!postSnap.exists()) {
return Response.json(
{ error: "Post not found" },
{ status: 404 }
);
}

// Update user's recentlyViewed list
const userRef = doc(db, "users", userId);
const userSnap = await getDoc(userRef);
const userData = userSnap.exists() ? userSnap.data() : {};
const recentlyViewed = userData.recentlyViewed || [];

// Store only post IDs (postId is already a string)
// Avoid duplicate entries & move to the top
let updatedList = recentlyViewed.filter((id) => id !== postId);
updatedList.unshift(postId);

// Keep only the latest 10 viewed posts
if (updatedList.length > 10) {
updatedList = updatedList.slice(0, 10);
}

await setDoc(userRef, { recentlyViewed: updatedList }, { merge: true });

return Response.json({
success: true,
recentlyViewed: updatedList,
});
} catch (error) {
console.error("Error in /api/posts/view:", error);
return Response.json(
{ error: "Internal server error", details: error.message },
{ status: 500 }
);
}
}

export async function GET(req) {
try {
const { searchParams } = new URL(req.url);
const userId = searchParams.get("userId");

if (!userId) {
return Response.json(
{ error: "userId is required" },
{ status: 400 }
);
}

const db = getServerDb();
const userRef = doc(db, "users", userId);
const userSnap = await getDoc(userRef);
if (!userSnap.exists()) {
return Response.json({ posts: [] });
}

const userData = userSnap.data();
const recentlyViewedIds = userData.recentlyViewed || [];

if (recentlyViewedIds.length === 0) {
return Response.json({ posts: [] });
}

// Fetch details of all recently viewed posts
const postsPromises = recentlyViewedIds.map(async (postId) => {
try {
const postDoc = await getDoc(doc(db, "posts", postId));
if (postDoc.exists()) {
return { id: postDoc.id, ...postDoc.data() };
}
return null;
} catch (err) {
console.error(`Error fetching post ${postId}:`, err);
return null;
}
});

const postsResult = await Promise.all(postsPromises);
const validPosts = postsResult.filter(Boolean);

return Response.json({ posts: validPosts });
} catch (error) {
console.error("Error in GET /api/posts/view:", error);
return Response.json(
{ error: "Internal server error", details: error.message },
{ status: 500 }
);
}
}

63 changes: 58 additions & 5 deletions app/dashboard/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export default function Dashboard() {
const [showSavedPosts, setShowSavedPosts] = useState(false);
const [showFeatureTour, setShowFeatureTour] = useState(false);
const [streak, setStreak] = useState(0);
const [recentlyViewedPosts, setRecentlyViewedPosts] = useState([]);

// ── Post editing state ───────────────────────────────────────────────────
const [editingId, setEditingId] = useState(null);
Expand Down Expand Up @@ -146,14 +147,14 @@ export default function Dashboard() {
try {
const seen = localStorage.getItem(FEATURE_TOUR_KEY);
if (!seen) setShowFeatureTour(true);
} catch {}
} catch { }
}, []);

const closeFeatureTour = () => {
setShowFeatureTour(false);
try {
localStorage.setItem(FEATURE_TOUR_KEY, "true");
} catch {}
} catch { }
};

// ── Firebase: posts ───────────────────────────────────────────────────────
Expand Down Expand Up @@ -226,6 +227,36 @@ export default function Dashboard() {
return () => unsubscribe();
}, [user]);

// ── Fetch recently viewed posts from API ────────────────────────────────────
useEffect(() => {
if (!user || activeTab !== "recently-viewed") return;

let isMounted = true;
const fetchRecentlyViewed = async () => {
try {
const res = await fetch(`/api/posts/view?userId=${user.uid}`);
if (!res.ok) throw new Error("Failed to fetch recently viewed posts");
const data = await res.json();
if (isMounted) {
setRecentlyViewedPosts(data.posts || []);
}
} catch (err) {
console.error(err);
}
};

fetchRecentlyViewed();

return () => {
isMounted = false;
};
}, [user, activeTab]);

const handleNavigateToPost = useCallback((postId) => {
window.location.hash = `#post-${postId}`;
window.dispatchEvent(new CustomEvent("dashboard-scroll-request"));
}, []);

// ── Firebase: active members ──────────────────────────────────────────────
useEffect(() => {
const membersQuery = query(
Expand Down Expand Up @@ -509,8 +540,8 @@ export default function Dashboard() {
postData.pollVotes = {};
}
await addDoc(collection(db, "posts"), postData);
try { captureEvent(EVENTS.POST_CREATED, { postType, tagCount: selectedTags.length }); } catch (_) {}
try { await updateStreak(user.uid); } catch (_) {}
try { captureEvent(EVENTS.POST_CREATED, { postType, tagCount: selectedTags.length }); } catch (_) { }
try { await updateStreak(user.uid); } catch (_) { }
// reset all composer fields on success
setContent("");
setSelectedTags([]);
Expand Down Expand Up @@ -650,9 +681,29 @@ export default function Dashboard() {

// ── Comment CRUD ──────────────────────────────────────────────────────────
const toggleComments = (postId) => {
setOpenCommentsFor((prev) => (prev === postId ? null : postId));
const nextState = openCommentsFor === postId ? null : postId;
setOpenCommentsFor(nextState);
setCommentDraft("");
setEditingComment(null);

if (nextState && user) {
fetch("/api/posts/view", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ userId: user.uid, postId }),
}).then(() => {
if (activeTab === "recently-viewed") {
fetch(`/api/posts/view?userId=${user.uid}`)
.then((res) => res.json())
.then((data) => setRecentlyViewedPosts(data.posts || []))
.catch((err) => console.error(err));
}
}).catch((err) => {
console.error("Failed to update recently viewed:", err);
});
}
};

const handleAddComment = async (post) => {
Expand Down Expand Up @@ -818,6 +869,8 @@ export default function Dashboard() {
posts,
filteredPosts,
trendingPosts,
recentlyViewedPosts,
onNavigateToPost: handleNavigateToPost,
activeTab,
setActiveTab,
highlightedPostId,
Expand Down
36 changes: 26 additions & 10 deletions components/dashboard/FeedColumn.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import PostCard from "./PostCard";
import PostComposer from "./PostComposer";
import RecentlyViewed from "./RecentlyViewed";

const FEED_TABS = [
{ id: "latest", label: "Latest Feed", icon: "▦" },
{ id: "trending", label: "Trending", icon: "📈" },
{ id: "recently-viewed", label: "Recently Viewed", icon: "🕒" },
{ id: "questions", label: "Questions", icon: "❔" },
{ id: "collaboration", label: "Collaborate", icon: "👥" },
];
Expand Down Expand Up @@ -73,6 +75,8 @@ export default function FeedColumn({
posts,
filteredPosts,
trendingPosts,
recentlyViewedPosts = [],
onNavigateToPost,
activeTab,
setActiveTab,
isMobile,
Expand Down Expand Up @@ -198,6 +202,18 @@ export default function FeedColumn({
</div>
)}

{activeTab === "recently-viewed" && (
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
<div style={{ fontSize: "0.78rem", color: "var(--text-muted)", padding: "0 2px" }}>
🕒 Posts you have recently viewed
</div>
<RecentlyViewed
posts={recentlyViewedPosts}
onNavigateToPost={onNavigateToPost}
/>
</div>
)}

{activeTab === "questions" && (
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
<div style={{ fontSize: "0.78rem", color: "var(--text-muted)", padding: "0 2px" }}>
Expand Down Expand Up @@ -237,16 +253,16 @@ export default function FeedColumn({
)}

{activeTab === "latest" && (
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
{filteredPosts.length === 0 ? (
<p style={{ color: "var(--text-muted)" }}>No posts found for this tag.</p>
) : (
filteredPosts.map((post, i) => (
<PostCard key={post.id} post={post} postIndex={i} isHighlighted={highlightedPostId === post.id} {...postCardProps} />
))
)}
</div>
)}
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
{filteredPosts.length === 0 ? (
<p style={{ color: "var(--text-muted)" }}>No posts found for this tag.</p>
) : (
filteredPosts.map((post, i) => (
<PostCard key={post.id} post={post} postIndex={i} isHighlighted={highlightedPostId === post.id} {...postCardProps} />
))
)}
</div>
)}
</section>
);
}
1 change: 1 addition & 0 deletions components/dashboard/LeftSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
const FEED_TABS = [
{ id: "latest", label: "Latest Feed", icon: "▦" },
{ id: "trending", label: "Trending", icon: "📈" },
{ id: "recently-viewed", label: "Recently Viewed", icon: "🕒" },
{ id: "questions", label: "Questions", icon: "❔" },
{ id: "collaboration", label: "Collaborate", icon: "👥" },
];
Expand Down
Loading